get value of function in json string - javascript

I'm tring to get some json to contain a function so that the option in the plugin gets the returned value of the function that was in the json string.
The set up is as such:
default.txt
[
{
"element":"a[href$='.jpg']",
"options":{
"label":"test"
}
},{
"element":"a#hover",
"options":{
"label":(function(ele){ return 'test'; })()
}
}
]
and the plug-in is
(function($) {
function defined(obj){return typeof(obj)!=='undefined';}
function evaluate(ele, obj) {
if(typeof obj === 'function') {
//alert('is function');
obj = obj(ele);
}else{
try{
//alert('thinking it may be a function still');
obj = eval("("+obj+"("+ele+"));");
//obj = (obj)(ele);
}catch(err){
//Handle errors here
//alert('not function');
}
}
return obj;
}
function debug(message) {
console.debug(message);
}
$.set = function(options){
var settings = $.extend({}, {}, options);
if(defined(settings.actions)){
$.each(settings.actions, function(index, value) {
$(value.element).do_action(defined(value.options)?value.options:null);
});
}
}
$.fn.do_action = function(options) {
// Add event handler to all matching elements
return this.each(function() {
var ele = $(this);
var settings = $.extend({}, $.fn.do_action.defaults, options);
var label= evaluate(ele, settings.label);
var message = "label:'" + label + "'";
debug('Tracking ' + action + ' ' + message);
});
};
$.fn.do_action.defaults = {
label : function(ele) { return ele.attr('href'); }
};
}(jQuery));
and the control is
$.getJSON('default.txt' , function(data){
$.set({
actions:data // this si where we pull in the options
});
});
I seems to work if I put in an alert inside the anonymous function, so from
"label":(function(ele){ return 'test'; })()
to
"label":(function(ele){ alert('test') })()
but after that I can't seem to get it to print right in the console as I only get the message
Tracking label:'(function(ele){ return 'test'; })
[EDIT]
Please stop saying that "you should not put code in json". If you work for Google and are their top level programmers getting paid the big super bucks then I'll listen to the why's, but I'm sorry if it's good for them, it'll work for me. Don't know what else to say, I already understand the point of why you should avoid it which I aim to do as well. There are times when you just can't and why i supect Google does the same.

Your label member is not a function; it's a value returned by a function. The final () at the end means "execute this function immediate whenever the value of label is requested and provide its return value as the value of label".
That said, if you must include functions in your JSON object (and from what you've said, you do) your best bet is to stringify-and-eval your functions. You should also be able to use raw functions in your JSON, like {foo : (function(){ ... }) }, though I've personally had a tricky time getting this right (but that's just me -- if you can get it right, go for it).
(As an aside, it's not possible to stringify functions from existing objects, but if you're getting your functions as text from user/programmer input, that's not an issue.)

Related

Pass a javascript function as part of a JSON [duplicate]

I've been looking around for a way to do this but can't seem to find anything, I have different configuration objects that I need to save as a text in variables for some processing later on, here is a sample:
object:
args.config.config = {
next: null,
final:[],
delimiter: '~', header: false,
step: function (row) {
var item = {
'line_code': row.data[0][0],
'order': row.data[0][1]
}
args.config.config.final.push(item);
},
complete: function (result) {
console.log('Reading data completed. Processing.');
return args.config.config.next(null, args.config.config.final);
},
error: function () {
console.log('There was an error parsing');
}'
}
I need to save this as a string, so something like:
args.config.config = "{object goes here}";
Without putting everything on one giant line or adding break line characters as this will be parsed later to be used in a config, and that will mess things up, any ideas?
UPDATE:
So changing them into text may not be the best solution, these configs will be stored in a mongo database, so it may take them as is (I have not tried it yet).
One of the other problems I was running into was that in the config object I had this:
final.push(item)
and
return next(null, final)
Which will be defined in another file using the config object:
other file:
exports.parse = function(args, next){//next is what I need to call in the config
var final = []; //this is the final referred to in the config object
....
Baby.parse(data, args.config)
}
So the return next(null, final) and final.push(result) have to refer the the var / function in the new file, but I have no idea how to get that to work, that't why I had to add a final array in the config object and a null next function, then assign it like so:
exports.parse = function(args, next){
args.config.next = next;
....
Baby.parse(data, args.config)
}
the object was calling it with the ugly line:
return args.config.config.next(null, args.config.config.final);
If anyone has a way around this, it would be much appreciated.
If you use JSON.stringify with a "replacer" function and
JSON.parse with a "reviver" function along with new Function(), you can do it:
I'm not sure I'm following the second (updated) question you have. Once the object is parsed back into an object, why can't you just initialize the next and final properties to valid objects before calling any of the object's methods? You can even add tests into that method that checks for the existence of final and next before returning anything.
var myObj = {
next: null,
final:[],
delimiter: '~',
header: false,
step: function (row) {
var item = {
'line_code': row.data[0][0],
'order': row.data[0][1]
};
args.config.config.final.push(item);
},
complete: function (result) {
console.log('Reading data completed. Processing.');
return args.config.config.next(null, args.config.config.final);
},
error: function () {
console.log('There was an error parsing');
}
};
// Stringify the object using a replacer function that will explicitly
// turn functions into strings
var myObjString = JSON.stringify(myObj, function(key, val) {
return (typeof val === 'function') ? '' + val : val;
});
// Now, parse back into an object with a reviver function to
// test for function values and create new functions from them:
var obj = JSON.parse(myObjString, function(key, val){
// Make sure the current value is not null (is a string)
// and that the first characters are "function"
if(typeof val === "string" && val.indexOf('function') === 0){
// Isolate the argument names list
var start = val.indexOf("(") + 1;
var end = val.indexOf(")");
var argListString = val.substring(start,end).split(",");
// Isolate the body of the function
var body = val.substr(val.indexOf("{"), val.length - end + 1);
// Construct a new function using the argument names and body
// stored in the string:
return new Function(argListString, body);
} else {
// Non-function property, just return the value
return val;
}
}
);
// Test the method:
obj.error(); // 'There was an error parsing' is written to console.
// Examine the object:
console.log(obj);

JavaScript - Save object with methods as a string

I've been looking around for a way to do this but can't seem to find anything, I have different configuration objects that I need to save as a text in variables for some processing later on, here is a sample:
object:
args.config.config = {
next: null,
final:[],
delimiter: '~', header: false,
step: function (row) {
var item = {
'line_code': row.data[0][0],
'order': row.data[0][1]
}
args.config.config.final.push(item);
},
complete: function (result) {
console.log('Reading data completed. Processing.');
return args.config.config.next(null, args.config.config.final);
},
error: function () {
console.log('There was an error parsing');
}'
}
I need to save this as a string, so something like:
args.config.config = "{object goes here}";
Without putting everything on one giant line or adding break line characters as this will be parsed later to be used in a config, and that will mess things up, any ideas?
UPDATE:
So changing them into text may not be the best solution, these configs will be stored in a mongo database, so it may take them as is (I have not tried it yet).
One of the other problems I was running into was that in the config object I had this:
final.push(item)
and
return next(null, final)
Which will be defined in another file using the config object:
other file:
exports.parse = function(args, next){//next is what I need to call in the config
var final = []; //this is the final referred to in the config object
....
Baby.parse(data, args.config)
}
So the return next(null, final) and final.push(result) have to refer the the var / function in the new file, but I have no idea how to get that to work, that't why I had to add a final array in the config object and a null next function, then assign it like so:
exports.parse = function(args, next){
args.config.next = next;
....
Baby.parse(data, args.config)
}
the object was calling it with the ugly line:
return args.config.config.next(null, args.config.config.final);
If anyone has a way around this, it would be much appreciated.
If you use JSON.stringify with a "replacer" function and
JSON.parse with a "reviver" function along with new Function(), you can do it:
I'm not sure I'm following the second (updated) question you have. Once the object is parsed back into an object, why can't you just initialize the next and final properties to valid objects before calling any of the object's methods? You can even add tests into that method that checks for the existence of final and next before returning anything.
var myObj = {
next: null,
final:[],
delimiter: '~',
header: false,
step: function (row) {
var item = {
'line_code': row.data[0][0],
'order': row.data[0][1]
};
args.config.config.final.push(item);
},
complete: function (result) {
console.log('Reading data completed. Processing.');
return args.config.config.next(null, args.config.config.final);
},
error: function () {
console.log('There was an error parsing');
}
};
// Stringify the object using a replacer function that will explicitly
// turn functions into strings
var myObjString = JSON.stringify(myObj, function(key, val) {
return (typeof val === 'function') ? '' + val : val;
});
// Now, parse back into an object with a reviver function to
// test for function values and create new functions from them:
var obj = JSON.parse(myObjString, function(key, val){
// Make sure the current value is not null (is a string)
// and that the first characters are "function"
if(typeof val === "string" && val.indexOf('function') === 0){
// Isolate the argument names list
var start = val.indexOf("(") + 1;
var end = val.indexOf(")");
var argListString = val.substring(start,end).split(",");
// Isolate the body of the function
var body = val.substr(val.indexOf("{"), val.length - end + 1);
// Construct a new function using the argument names and body
// stored in the string:
return new Function(argListString, body);
} else {
// Non-function property, just return the value
return val;
}
}
);
// Test the method:
obj.error(); // 'There was an error parsing' is written to console.
// Examine the object:
console.log(obj);

Struggling to return an object from a JavaScript callback function

I am working with the AddThis JavaScript API. The method I am struggling with is documented here:
http://support.addthis.com/customer/portal/articles/1137944-getting-counter-values-dynamically
I can obtain the data I require from the "obj" object - but only within that method. I can't seem to return the data to a global variable that I can use in my jQuery loop. The problem is I have a limited understanding of Javascript objects. Here is my code:
addthis.addEventListener('addthis.ready', addthisReady);
function addthisReady() {
var myobj = {};
addthis.sharecounters.getShareCounts({service: ['facebook', 'twitter', 'pinterest'], countUrl: 'http://www.addthis.com/'}, function(obj) {
console.log(obj); // OK
myobj = obj;
});
console.log(myobj); // Not OK
}
My ultimate goal is to have multiple article links on a page and then use jQuery and this API method to append the total share counts to their linked titles. EG;
Article Link X (22 Shares)
Article Link Y (13 Shares)
Article Link Z (13 Shares)
Any help would be grand.
CJ
PROGRESS UPDATE - Almost there...
The code below factors in your advice and an example provide by the API vendors. It is almost there, but the callback randomly updates only one of the elements in the Each loop.
The example code - commented out - indicated that multiple calls to the method should be possible.
Here is the code:
$(document).ready(function(){
addthis.addEventListener('addthis.ready', addthisReady);
function addthisReady() {
/*
addthis.sharecounters.getShareCounts('facebook', function(obj) {
document.getElementById('basic-example').innerHTML = '<code>'+JSON.stringify(obj, undefined, 4)+'</code>';
});
addthis.sharecounters.getShareCounts(['facebook', 'twitter', 'pinterest'], function(obj) {
document.getElementById('multiple-services').innerHTML = '<code>'+JSON.stringify(obj, undefined, 4)+'</code>';
});
addthis.sharecounters.getShareCounts({service: 'facebook', countUrl: 'http://www.addthis.com/'}, function(obj) {
document.getElementById('specific-url').innerHTML = '<code>'+JSON.stringify(obj, undefined, 4)+'</code>';
});
addthis.sharecounters.getShareCounts({service: ['facebook','twitter'], countUrl: 'http://www.addthis.com/'}, function(obj) {
document.getElementById('specific-url-multiple-services').innerHTML = '<code>'+JSON.stringify(obj, undefined, 4)+'</code>';
});
*/
$('.ico-shares').each(function(index) {
var elem = this;
var share_url = $(elem).attr('href').split('#')[0];
var shareLabel = $(elem).text();
var total_count = 0;
//Request Counts
addthis.sharecounters.getShareCounts({service: ['facebook', 'twitter'], countUrl: share_url}, function(obj) {
for (var key in obj)
{
total_count += obj[key].count;
}
if (total_count > 0)
{
shareLabel = total_count + ' Share';
}
if (total_count > 1)
{
shareLabel = total_count + ' Shares';
}
alert(shareLabel);
$(elem).text(shareLabel);
});
});
}
});
My URL is here: http://banarra.cjweb.com.au/html/news_article.php
Note there are 3 x "More News" articles at the bottom, but only one has it's Share link updated.
Thanks very much for your time.
CJ
The code you've posted isn't going to be the problem.
If you sent your request, and then waited a few seconds, and then checked the value of the variable using the console, you should see it set to the value you were expecting.
The problem is asynchronicity.
See, when you send a request off to the server (ie: when that function actually fires), it doesn't sit there and wait for it to come back, before moving to your next line of code.
So if you have code that looks like:
var myObj = {};
getAsyncData(function myCallback (o) { myObj = o; });
console.log(myObj);
What's actually going to happen is that function is going to fire, and your code is going to keep on running.
The server hasn't returned your data, so myObj === {}.
Some time later, the server will return your data, and run that function that you gave it.
So if you come back in some time:
setTimeout(function () { console.log(myObj); }, 10000); // 10 sec
Then you should see it's being set.
The trick here is that you actually need to trigger stuff in your async code.
The rest of your program doesn't know anything is changed or anything is new, and it certainly hasn't sat and waited for it.
var myObj = {};
getAsyncData(function (o) { myObj = o; doStuff(myObj); });
var doStuff = function (obj) { console.log("Back from the server"); console.log(obj); };
To that end, I'd suggest looking at the link in the comments.
EDIT
To better grasp the problem (not the many solutions), imagine your async calls used a setTimeout to fire your function and pass in the data.
You wouldn't expect your whole JS app to sit and wait for one change in a setTimeout.
var data_is_set = false,
get_data = function (callback) {
var data = true,
time_to_wait = 3000; // 3sec
setTimeout(function () { callback(data); }, time_to_wait);
};
get_data(function (val) { data_is_set = val; });
console.log(data_is_set); // false
The rest of the program is going to run, until that time goes by, and then your function gets called.
Nobody else knows what happened, so you need to make functions which now trigger updates, that you can call inside of your callback.
Can you just return obj?
var myobj = addthis.sharecounters.getShareCounts({service: ['facebook', 'twitter', 'pinterest'], countUrl: 'http://www.addthis.com/'}, function(obj) {
console.log(obj); // OK
return obj;
});

Javascript, possible to pass undeclared method parameters without eval?

Ok, difficult to understand from the title only. Here is an example. I want a function to refer to a variable that is "injected" automagically, ie:
function abc() {
console.log(myVariable);
}
I have tried with:
with({myVariable: "value"}) { abc() }
but this doesn't work unless abc is declared within the with block, ie:
with({myVariable: "value"}) {
function abc() {
console.log(myVariable);
}
abc(); // This will work
}
So the last piece will work, but is it possible to fake the with statement, or do I have to force the developers to declare their function calls in a with statement?
Basically the call I want to do is:
doSomething({myVariable: "value"}, function() {
console.log(myVariable);
});
Ofcourse, I am aware I could pass this is a one parameter object, but that is not what I am trying to do:
doSomething({myVariable: "value"}, function(M) {
console.log(M.myVariable);
});
Further more, I am trying to avoid using eval:
with({myVariable: "value"}) {
eval(abc.toString())(); // Will also work
}
Is this not supported at at all beyond eval in Javascript?
JavaScript does not provide any straightforward way to achieve the syntax you're looking for. The only way to inject a variable into a Lexical Environment is by using eval (or the very similar Function constructor). Some of the answers to this question suggest this. Some other answers suggest using global variables as a workaround. Each of those solutions have their own caveats, though.
Other than that, your only option is to use a different syntax. The closest you can get to your original syntax is passing a parameter from doSomething to the callback, as Aadit M Shah suggested. Yes, I am aware you said you don't want to do that, but it's either that or an ugly hack...
Original answer (written when I didn't fully understand the question)
Maybe what you're looking for is a closure? Something like this:
var myVariable = "value";
function doSomething() {
console.log(myVariable);
};
doSomething(); // logs "value"
Or maybe this?
function createClosure(myVariable) {
return function() {
console.log(myVariable);
};
}
var closure = createClosure("value");
closure(); // logs "value"
Or even:
var closure = function(myVariable) {
return function() {
console.log(myVariable);
};
}("value");
closure(); // logs "value"
I asked a similar question a long time ago: Is it possible to achieve dynamic scoping in JavaScript without resorting to eval?
The short answer is no, you can't achieve dynamic scoping without resorting to eval. The long answer is, you don't need to.
JavaScript doesn't support dynamic scoping, but that's not an issue because you can make your free variables parameters of the function that they belong to.
In my humble opinion this is the best solution:
function doSomething(context, callback) {
callback(context);
}
doSomething({myVariable: "value"}, function(M) {
console.log(M.myVariable);
});
However since you don't want to write a formal parameter, the next best thing is to use this instead:
function doSomething(context, callback) {
callback.call(context);
}
doSomething({myVariable: "value"}, function() {
console.log(this.myVariable);
});
Another option would be to manipulate the formal parameter list of the program as follows:
function inject(func, properties) {
var args = [], params = [];
for (var property in properties) {
if (properties.hasOwnProperty(property)) {
args.push(properties[property]);
params.push(property);
}
}
return Function.apply(null, params.concat("return " + func.toString()))
.apply(null, args);
}
Now we can use this inject method to inject properties into a function as follows:
function doSomething(context, callback) {
var func = inject(callback, context);
func();
}
doSomething({myVariable: "value"}, function() {
console.log(myVariable);
});
See the demo: http://jsfiddle.net/sDKga/1/
Note: The inject function will create an entirely new function which will not have the same lexical scope as the original function. Hence functions with free variables and partially applied functions will not work as expected. Only use inject with normal functions.
The Function constructor is kind of like eval but it's much safer. Of course I would advise you to simply use a formal parameter or this instead. However the design decision is your choice.
Try:
function doSomething(vars, fun) {
for (var key in vars) { // set the variables in vars
window[key] = vars[key];
}
fun.call(); // call function
for (var key in vars) { // remove the variables again. this will allow only the function to use it
delete window[key];
}
}
Set global variables that can then be received inside of fun
The JSFiddle: http://jsfiddle.net/shawn31313/MbAMQ/
Warning: disgusting code ahead
function callWithContext(func, context, args) {
var oldProperties = {};
for(var n in context) {
if(context.hasOwnProperty(n)) {
var oldProperty = Object.getOwnPropertyDescriptor(self, n);
oldProperties[n] = oldProperty;
(function(n) {
Object.defineProperty(self, n, {
get: function() {
if(arguments.callee.caller === func) {
return context[n];
}
if(!oldProperty) {
return;
}
if(oldProperty.get) {
return oldProperty.get.apply(this, arguments);
}
return oldProperty.value;
},
set: function(value) {
if(arguments.callee.caller === func) {
context[n] = value;
}
if(!oldProperty) {
return;
}
if(oldProperty.set) {
return oldProperty.get.apply(this, arguments);
} else if(!oldProperty.writable) {
var fakeObject = {};
Object.defineProperty(fakeObject, n, {value: null, writable: false});
fakeObject[n] = value; // Kind of stupid, but…
return;
}
oldProperty.value = value;
}
});
})(n);
}
}
func.apply(this, args);
for(var n in context) {
if(context.hasOwnProperty(n)) {
if(oldProperties[n]) {
Object.defineProperty(self, n, oldProperties[n]);
} else {
delete self[n];
}
}
}
}
This is vomitously horrendous, by the way; don’t use it. But ew, it actually works.
i don't see why you can't just pass the info in or define a single global, but i think that would be best.
that said, i am working on a Module maker/runner that allows sloppy/dangerous code to execute without interference to the host environment. that provides the opportunity to re-define variables, which can be passed as an object.
this does use eval (Function() technically) but it can run in "use strict", so it's not too crazy/clever.
it doesn't leave behind artifacts.
it also won't let globals get hurt.
it's still a work in progress, and i need to iron out a couple minor details before i vouch for security, so don't use it for fort knox or anything, but it's working and stable enough to perform the operation asked for.
tested in ch28, FF22, IE10:
function Module(strCode, blnPreventExtensions, objWhitelist, objExtend) {
var __proto__=self.__proto__, pbu=self.__proto__, str=strCode, om=[].map, wasFN=false,
params = {Object:1}, fnScrubber, natives= [ Object, Array, RegExp, String, Boolean, Date] ,
nativeSlots = [],
preamble = "'use strict';" ,
inherited="__defineGetter__,__defineSetter__,__proto__,valueOf,constructor,__lookupGetter__,__lookupSetter__",
late = inherited +
Object.getOwnPropertyNames(__proto__||{}) + Object.getOwnPropertyNames(window);
late.split(",").sort().map(function(a) {
this[a] = 1;
}, params);
preamble+=";var "+inherited+";";
//turn functions into strings, but note that a function was passed
if(str.call){wasFN=true; str=String(str); delete params.Object; }
objExtend=objExtend||{};
var vals=Object.keys(objExtend).map(function(k){ return objExtend[k]; })
// build a usable clone of Object for all the new OOP methods it provides:
var fakeOb=Object.bind();
(Object.getOwnPropertyNames(Object)||Object.keys(Object)).map(function(a){
if(Object[a] && Object[a].bind){this[a]=Object[a].bind(Object); } return this;
},fakeOb)[0];
//allow "eval" and "arguments" since strict throws if you formalize them and eval is now presumed safe.
delete params.eval;
delete params.arguments;
params.hasOwnProperty=undefined;
params.toString=undefined;
params['__proto__']={};
__proto__=null;
Object.keys(objWhitelist||{}).map(function ripper(a,b){
b=this[a];
if(typeof b!=='object'){
delete this[a];
}
}, params);
// var ok=Object.keys.bind(Object);
// prevent new prototype methods from being added to native constructors:
if (blnPreventExtensions) {
natives.forEach(function(con, i) {
var proto=con.prototype;
Object.getOwnPropertyNames(proto).map(function(prop){
if(proto[prop] && proto[prop].bind ){ this[prop]=proto[prop];}
}, nativeSlots[i] = {});
delete con.constructor;
delete con.prototype.constructor;
}); //end con map()
} /* end if(blnPreventExtensions) */
//white-list harmless math utils and prevent hijacking:
delete params.Math;
if(blnPreventExtensions){Object.freeze(Math);}
//prevent literal constructors from getting Function ref (eg: [].constructor.constructor, /./.constructor.constructor, etc...):
Function.prototype.constructor = null;
try {
//generate a private wrapper function to evaluate code:
var response = Function(
Object.keys(objExtend) + (vals.length?",":"") +
Object.keys(params).filter(/./.test, /^[\w\$]+$/), // localize most globals
preamble + " return " + str.trim() // cram code into a function body with global-blocking formal parameters
);
// call it with a blank this object and only user-supplied arguments:
if (blnPreventExtensions) { //( user-land code must run inside here to be secure)
response = response.apply({}, vals.concat(fakeOb)).apply({}, [].slice.call(arguments,4) );
}else{
response = response.apply({}, vals.concat(fakeOb));
}
} catch (y) {
response = y + "!!";
} /* end try/catch */
if (blnPreventExtensions) {
om.call(natives, function(con, i) {
var pro=con.prototype;
//remove all proto methods for this con to censor any additions made by unsafe code:
Object.getOwnPropertyNames(pro).map(function(a){ try{delete pro[a];}catch(y){}});
//restore all original props from the backup:
var bu = nativeSlots[i];
om.call(Object.keys(bu), function(prop){ con.prototype[prop]=bu[prop]; }, bu);
}); //end con map()
} /* end if(blnPreventExtensions) */
//restore hidden Function constructor property:
Function.prototype.constructor = Function;
return response;
} /* end Module() */
/////////////////////////////////////////////////////////////
function doSomething(context, fn){
console.log(myVariable);
return myVariable;
}
//use 1:
alert( Module(doSomething, true, {console:1}, {myVariable: "value123"} ) );// immed
//use2:
var fn=Module(doSomething, false, {console:1}, {myVariable: "value123"} );// as function
alert(fn);
alert(fn());
again, i think OP would be best off not doing things later than need be, but for the sake of comprehensiveness and inspiration i'm putting this out there in good faith.
You need to use call() to construct a context, as in:
var f=function(){
console.log(this.foo);
};
f.call({foo:'bar'})
will print "bar"
You can avoid using eval() in calling the function, if you are willing to use it in doSomething():
function abc() {
console.log(myVariable);
}
// Prints "value"
callWith({ myVariable: "value" }, abc);
function callWith(context, func) {
for(var i in context) eval('var ' + i + ' = context[i];');
eval('(' + func.toString() + ')')();
}
Have a look at this post.
Have a look at goog.partial, scroll a little bit up to see the description of what it does:
Here is an implementation of it:
var b = goog.partial(alert, 'Hello world!');
b();//alerts "Hello world!"
In the example it passes the function alert with parameter "Hello world!" but you can pass it your own function with multiple parameters.
This allows you to create a variable that points to a function that is always called with a certain paramater. To use parameters in a function that are not named you can use arguments:
function test(){
console.log(arguments);//["hello","world"]
}
test("hello","world");

Does JavaScript have an equivalent way to implement c# style auto properties without massive overhead?

I'm curious if JavaScript has a way to bind a function when a property is changed without an enormous overhead like watching all the auto-properties with a timer, but without setting them via a function call. For example, I know you could do something like:
var c = new (function () {
this.Prop = 'test';
this.Prop_get = function (value) {
return('Prop = ' + this.Prop);
};
this.Prop_set = function (value) {
if (value != 'no') {
this.Prop = value;
}
};
})();
document.write(c.Prop_get());
document.write('<BR />');
c.Prop_set('no');
document.write(c.Prop_get());
document.write('<BR />');
c.Prop_set('yes');
document.write(c.Prop_get());
document.write('<BR />');
But I'm looking for some way to allow the following to produce the same result:
document.write(c.Prop);
document.write('<BR />');
c.Prop = 'no';
document.write(c.Prop);
document.write('<BR />');
c.Prop = 'yes';
document.write(c.Prop);
document.write('<BR />');
With any changes to the pseudoclass other than adding a timer to watch the Prop property for changes or similarly high-overhead solutions.
Any solution to this issue comes down to what it is that you need to support.
If you require IE6-IE8, it's probably more sane to resort to timers or horrible abuse of the DOM to make changes to hidden DOM objects, which will fire listenable events, etc...
There are a few blogs which have talked about their efforts to squeeze these browsers into conformity with some kind of mutation-aware library.
Results and caveats vary.
If you're talking about ES5-compliant browsers, most support "get" and "set" keywords directly inside of objects.
This might lead to even cleaner constructors/interfaces than C#, because constructors can be as simple as var a = {};, but you also get the magic-methods, rather than the Java list of getX, getY, z, and the headache of trying to remember what's a method and what's a property, when you get to the interface.
Seriously, this is kinda pretty:
var person = {
person_name : "Bob",
get name () { return this.person_name; },
set name (value) {
console.log("But my parents named me " + this.person_name + "!");
}
};
person.name;
person.name = "Mark";
But there's an issue here: person.person_name isn't private at all.
Anybody could swoop in and change that.
Not to fret -- get and set don't actually have to operate on properties of the object.
var Person = function (name, age) {
// we don't need to save these; closures mean they'll be remembered as arguments
// I'm saving them as `private_*` to illustrate
var private_name = name,
private_age = age;
var public_interface = {
get name () { return private_name; },
set name (value) { console.log("Nope!"); },
get age () { return private_age; },
set age (value) { console.log("Nope!"); },
set court_appointed_name (value) {
console.log("If I must...");
private_name = value;
}
};
return public_interface;
};
var mark = Person("Mark", 32);
mark.name; // "Mark";
mark.name = "Bubba"; // log: "Nope!";
mark.name; // "Mark";
mark.court_appointed_name = "Jim-Bob"; // log: "If I must..."
mark.name; // "Jim-Bob"
You could also force assignments to pass in objects, with auth-tokens, et cetera.
mark.name = {
value : "Jimmy Hoffa",
requested_by : system.user.id,
auth : system.user.auth.token
};
This is all fantastic, isn't it?
Why aren't we doing it?
Browser support.
Problem is this requires brand new syntax: all objects are defined as key-value pairs.
Messing with the syntax means any non-supporting browser will crash and burn unless you wrap your entire program in a try/catch (which is performance-suicide).
You could do one try-catch test, and lazy-load the awesome interface, versus fugly workarounds, at page-load, which is the right way to go about it, but now you're developing two versions of the application.
Or three versions, as case may be (new browsers, intermediate-browsers like FF3, and hacks for Ghetto_IE).
Intermediate browsers used {}.__defineGetter__ and {}.__defineSetter__.
Object.prototype.defineProperty (/.defineProperties) are the methods which instill hope of IE compatibility, until you realize that older versions of IE only supported the mutations on DOM objects (attached to the actual DOM tree), hence the headaches. Hooray.
I found the solution to this after coming across this link relating to getters and setters. Here is a generic method of applying properties to objects I put together as a result if anyone is interested in it:
Object.prototype.Property = function (name, fn) {
if (fn.hasOwnProperty('get')) { this.__defineGetter__(name, fn.get); }
else { this.__defineGetter__(name, function () { throw ('Cannot read property ' + name + '.'); }); }
if (fn.hasOwnProperty('set')) { this.__defineSetter__(name, fn.set); }
else { this.__defineSetter__(name, function () { throw ('Cannot write property ' + name + '.'); }); }
};
function C() {
var _Field = 'test';
this.Property('Field', {
get: function () {
return ('Field = ' + _Field);
},
set: function (value) {
if (value != 'no') {
_Field = value;
}
}
});
};
C.prototype.constructor = C;
var c = new C();
document.write(c.Field);
document.write('<BR />');
c.Field = 'no';
document.write(c.Field);
document.write('<BR />');
c.Field = 'yes';
document.write(c.Field);
document.write('<BR />');
Edit: A JQuery-friendly Object.prototype.Property function like the above:
Object.defineProperty(Object.prototype, 'Property', {
enumerable: false,
value: function (name, fn) {
if (fn.hasOwnProperty('get')) { this.__defineGetter__(name, fn.get); }
else { this.__defineGetter__(name, function () { throw ('Cannot read property ' + name + '.'); }); }
if (fn.hasOwnProperty('set')) { this.__defineSetter__(name, fn.set); }
else { this.__defineSetter__(name, function () { throw ('Cannot write property ' + name + '.'); }); }
}
});
And a working JSFiddle.

Categories