I have a bit of JavaScript code that is specified in a configuration file on the server-side. Since I can't specify a JavaScript function in the configuration language (Lua), I have it as a string. The server returns the string in some JSON and I have the client interpret it using a clean-up function:
parse_fields = function(fields) {
for (var i = 0; i < fields.length; ++i) {
if (fields[i].sortType) {
sort_string = fields[i].sortType;
fields[i].sortType = eval(sort_string);
}
return fields;
}
};
So basically it just evaluates sortType if it exists. The problem is that Firebug is reporting a "Syntax error" on the eval() line. When I run the same steps on the Firebug console, it works with no problems and I can execute the function as I expect. I've tried some different variations: window.eval instead of plain eval, storing the sortType as I've done above, and trying small variations to the string.
A sample value of fields[i].sortType is "function(value) { return Math.abs(value); }". Here's the testing I did in Firebug console:
>>> sort_string
"function(value) { return Math.abs(value); }"
>>> eval(sort_string)
function()
>>> eval(sort_string)(-1)
1
and the error itself in Firebug:
syntax error
[Break on this error] function(value) { return Math.abs(value); }
The last bit that may be relevant is that this is all wrapped in an Ext JS onReady() function, with an Ext.ns namespace change at the top. But I assumed the window.eval would call the global eval, regardless of any possible eval in more specific namespaces.
Any ideas are appreciated.
To do what you want, wrap your string in parentheses:
a = "function(value) { return Math.abs(value);}";
b = eval("("+a+")");
b(-1);
The parentheses are required because they force the thing inside them to be evaluated in an expression context, where it must be a function-expression.
Without the parentheses, it could instead be a function declaration, and it seems as if it is sometimes being parsed that way - this could be the source of the odd/inconsistent behaviour you're describing.
Compare this function declaration:
function foo(arg) {}
with this function-expression:
var funcExpr = function foo(arg) {};
It also has to be a function-expression if it doesn't have a name. Function declarations require names.
So this is not a valid declaration, because it's missing its name:
function (arg) {}
but this is a valid, anonymous function-expression:
var funcExpr = function(arg) {};
Related
I made a temperature conversion function and I am curious if there is anyway to automatically convert an argument into a string.
For instance, convertTemp(72, F) --> convertTemp(72, 'F') and then start the function.
I know I can just enter the argument in as a string, but It got me wondering if there was a way to turn a (somehow erroneously entered) variable declaration into a string without having to deal with the Reference error saying that the argument is not defined (declared).
I've been looking through the toString() method, String(), etc. but they all convert values not an implicit variable like format would be.
If it's not possible that's fine, just a curiosity.
Thanks!
function convertTemp(temperature, format) {
if (format == 'C') return CtoF();
else if (format == 'F') return FtoC();
else return 'Invalid Metric';
function CtoF() {
let F = Math.floor((1.8) * temperature + 32);
return F;
}
function FtoC() {
let C = Math.floor(((temperature - 32) * 5) / 9);
return C;
}
}
TLDR; function is entered as: convertTemp(72, F) instead of convertTemp(72, 'F'), can this be automatically corrected?
The answer to your question is technically "no".
You'd have to do some pretty hacky stuff to avoid a ReferenceError and none of it would be efficient and would be bound to cause bugs, but just for fun I've come up with just the kind of hack that should make most programmers cringe.
Disclaimer: Under no circumstances would I recommend ever doing this
First let's consider a function that simply outputs its arguments
function doStuff(...args){
console.log("Args:", ...args)
}
If you try and pass this function a variable, that variable has to be defined somewhere. As other users have stated, a global variable would be the only way to ensure the variable is defined in any scope, and we will be using that later.
If you surround a reference error with try/catch, then it's possible to catch the reference error's message. Then using regex you can extract the missing parameter, which luckily will already be a string, add it to the global scope, then simply call the function again.
try {
doStuff(A)
} catch (e) {
const [,arg] = e.message.match(/^(.*?) is not defined$/i)
window[arg] = arg
doStuff(A)
}
But that's redundant and pointless, so now let's take this logic and put it in a function that will automatically define the variable for you in the global scope then call the function again like so:
function wrapError(fn){
try {
fn()
}catch(e){
const [, arg] = e.message.match(/^(.*?) is not defined$/i)
window[arg] = arg
wrapError(fn)
}
}
The unfortunate caveat here is that you have to pass a function to wrapError, or else doStuff will evaluate inline and will throw its error outside the try/catch block.
wrapError(()=>doStuff(A, B, C))
Finally you end up with the following, which was tested on Chrome on Mac. It's possible that the error message thrown will have a different format or structure and this regex isn't that robust, so keep that in mind.
function doStuff(...args){
console.log("Args:", ...args)
}
function wrapError(fn){
try {
fn()
}catch(e){
const [,arg] = e.message.match(/^(.*?) is not defined$/i)
window[arg] = arg
wrapError(fn)
}
}
// With wrapper
wrapError(()=>doStuff(PLZ, DONT, DO, THIS))
// Without wrapper
doStuff(Hello, Error) // ReferenceError
Conclusion: Never do this and I hope nobody ever takes this seriously.
It is not possible without a preprocessor, which would clean up the code in advance.
You could take a const for 'F'. But I would not recommend it.
const F = 'F';
I have an angular service fetching some JSON data, and in this data I have a function as a string (building it server side and passing it to angular), something like:
{
"fn": "function( data ) { data.rendered = data.value; }"
}
Is it possible to parse this and use it as an expression with angular? I.e.
settings.myCustomFunction = serviceData.fn;
I've attempted to do this with different combinations of eval, $eval, $parse, but I haven't had any luck with any of them. They all either throw errors about "Unexpected *" or don't throw errors, but also don't seem to do anything.
You could wrap the string in parentheses to avoid the syntax error.
eval("(" + fnString + ")")
This makes it a syntactically valid JavaScript statement which is composed of a single function expression that you can pass around freely.
An anonymous function by itself, without any parentheses or LHS operators (like the commonly used assignment operator =) acting upon it, is not a syntactically valid JavaScript statement. That's why you'll see IIFEs (immediately invoked function expressions) wrapped up in parentheses in JS:
(function() {
// ...
})()
If you really want to get eval to work, then you can do this:
var settings = {};
eval("settings.myCustomFunction = " + serviceData.fn);
Using eval in this way of course opens your app up to possible malicious script injections.
You could do user new Function():
settings.myCustomFunction = new Function('argument',functionbody);
To execute:
settings.myCustomFunction();//
You might want to check out eval.call. This allows you to not care what the arguments are and use any arguments from strings. Of course to call it you must use the same arguments, BUT, you could string-drive that too, by concatting the args together when you eval.call it.
{
"fn": "function( data ) { return data + 1; }"
}
Example:
var serviceData = {
fn: '(function (data) {return data + 1;})'
};
var data = 44;
var result = window.eval.call(window, serviceData.fn)(data);
console.log(result); // 45
I am just learning NodeJS and/or PhantonJS.
As a programmer with a lot of C experience, I do not like the way NodeJs code is written and find it a bit messy/unreadable. (Sorry if I ruffled any feathers)
In spirit of cleaning up the code, I was trying to do this and found a block.
In C or C++, we should be able to pass a function by name but in NodeJS/PhantomJS it does not seem to work.
Am I doing somthing wrong ?
Can someone explain to me how this is looked at by the Javascript interpreter ?
var page = require('webpage').create();
var printS = function (s) {
console.log(s);
phantom.exit();
}
/* This works */
page.open('http://net.tutsplus.com', function (s) {
console.log(s);
phantom.exit();
});
/* This does not work
page.open('http://net.tutsplus.com', printS(status));
*/
/*But this works
page.open('http://net.tutsplus.com', function (s) { printS(s);} );
*/
page.open('http://net.tutsplus.com', printS(status));
fails because you're not passing the function but rather the result of invoking the function on status. If you want to pass the function, you'd do it this way
page.open('http://net.tutsplus.com', printS);
I thought it might be helpful to have a more extensive explanation. Let's start simple:
In JavaScript, we have values and variables. Variables are containers for values. Almost everywhere where we can use values, we can use variables.
In JavaScript source code, we express values through literals, e.g. the number literal 42. We can directly pass that value to a function:
f(42);
Additionally, instead of passing the value directly, we can pass a variable to the function:
var v = 42;
f(v);
That is, we can substitute values with variables.
Lets consider
var printS = function() { ... };
This clearly is a variable whose value is a function. If we'd directly pass that value to a function (i.e. we pass a function to a function), it would look like:
f(function() { ... }); // similar to f(42)
That's exactly what you have in your first case:
page.open('http://net.tutsplus.com', function (s) {
// ...
});
Since we know that we can replace values with variables, we can just substitute function() { ... } with printS:
var printS = function() { ... }; // similar to var v = 42;
f(printS); // similar to f(v)
So your example would become
page.open('http://net.tutsplus.com', printS);
What is wrong with
page.open('http://net.tutsplus.com', printS(status));
then?
Notice that you added additional characters after printS, namely (status). They don't appear in the your first example where you inlined the function:
page.open('http://net.tutsplus.com', function (s) {
// ...
});
There is no (status) here. Hence these two constructs cannot be not equivalent.
page.open accepts a function value as second argument, but printS(status) doesn't evaluate to the function printS, it calls the function printS and passes the return value to page.open.
Why does
page.open('http://net.tutsplus.com', function (s) { printS(s);} );
work?
Lets remove the content and the argument of the function, and it becomes:
page.open('http://net.tutsplus.com', function () { ... } );
That looks exactly like one of the examples above. function () { ... }, is a function literal, so to speak. It creates a function value. There are no (...) after it which would call the function.
This doesn't work as you hope because page.open wants a function as its second argument... this callback pattern is very common in JavaScript. In your doesn't-work example, printS is being called with status as its argument, and it returns undefined. As undefined is not a function, it doesn't behave as you wish.
In your browser console or the node repl:
> printS = function (s) { console.log(s); };
function (s) { console.log(s); }
> typeof printS('hi');
hi
"undefined"
> typeof function (s) { printS(s); };
"function"
Another thing to know about JavaScript is that its dynamic typing and fairly generous type coercion can result in baffling behavior with no helpful errors to point you towards the root cause of your problem. A debugger or copious use of console.log() is frequently helpful in understanding these sort of problems.
May need a Javascript language lawyer for this one:
var s1 = "{\"x\":\"y:z\"}"
var o = JSON.parse(s1)
var s2 = JSON.stringify(o)
$('span#s').text(s1);
$('span#s2').text(s2);
if (s1 === s2) {
$('span#areEqual').text('s1 === s2')
} else {
$('span#areEqual').text('s1 !== s2')
}
JSON.parse(s2) // okay
$('span#jsonParse').text("JSON.parse(s2) okay")
eval(s2) // bad mojo!
$('span#eval').text("eval(s2) okay")
eval("("+s2+")") // bad mojo, too!
$('span#eval2').text("eval((s2)) okay")
eval fails on s1, s2, and "("+s2+")".
jsFiddle here.
Your problem is that you mixing two unrelated things.
eval() is built-in javascript function, which main purpose is to interpret string of javascript code (thus make potentional security hole)
JSON.parse() function is for parse JSON string. Although very simmilar, do not make mistake, JSON is not Javascript and there are tiny differences. You should not use eval() for parsing JSON
What are the differences between JSON and JavaScript object?
$eval is automatically evaluated against a given scope.
For example:
$scope.a = 2;
var result = $scope.$eval('1+1+a');
// result is 4
$parse does not require scope. It takes an expression as a parameter and returns a function. The function can be invoked with an object that can resolve the locals:
For example:
var fn = $parse('1+1+a');
var result = fn({ a: 2 });
// result is 4
When you use eval for parsing JSON you need to wrap your expression with parentheses
eval('(' + s2 + ')');
jsfiddle
Check out what the specification says about JSON and eval
http://www.json.org/js.html
Notice this part specifically
The eval function is very fast. However, it can compile and execute
any JavaScript program, so there can be security issues. The use of
eval is indicated when the source is trusted and competent. It is much
safer to use a JSON parser. In web applications over XMLHttpRequest,
communication is permitted only to the same origin that provide that
page, so it is trusted. But it might not be competent. If the server
is not rigorous in its JSON encoding, or if it does not scrupulously
validate all of its inputs, then it could deliver invalid JSON text
that could be carrying dangerous script. The eval function would
execute the script, unleashing its malice.
JSON is just a javascript object, and nothing more. Valid javascript could include functions, execution blocks, etc. If you just eval() a string, it could have code it in. JSON will parse if it's just JSON, but you can't know for sure by just stuffing it into eval. For example
var s = "(function(){ /* do badStuff */ return {s: 123, t: 456}; })()";
var result = eval(s);
Would give you a var result with the contents {s: 123, t: 456} but would also execute any code hidden in your function. If you were taking this input from elsewhere, code could be executing and not actually break anything on your end. Now the same example with JSON.parse
var result = JSON.parse(s);
It throws an error with the message:
Uncaught SyntaxError: Unexpected token (
So the parse saves you from remote code execution here, even if someone tried to sneak it in.
eval wasn't an expression - i've updated it to evaluate eval(s2 === s1);
Otherwise it will try & execute what's within the eval & stop execution.
eval() attempts to evaluate a block of JavaScript code. If you had created a script file that started with the same text, you would have gotten the same error. In that context, I believe the braces signify a compound statement, as in an if-statement or for-statement body, but at the beginning of the compound statement is a string followed by a colon, which is not valid syntax.
If you wanted a string that would evaluate to an object, you'd have to enclose the object expression in parentheses to make it explicit that it's an expression. But as apocalypz says, you should not attempt to eval JSON. It's wrong on so many levels.
if you really want to use eval instead of JSON.parse() for parsing JSON then you should write something like
var o2; // declare o2 out of eval in case of "use strict"
eval("o2 = "+s1); // parse s1 and the assignment to the local o2
console.log(o2); // enjoy the local variable :)
...
I'm trying to alert any JavaScript object as a string, in a function. This means if the parameter given to the function is window.document, the actual object, it should alert "window.document" (without quotes) as a literal string.
The following calls...
example(window);
example(window.document);
example(document.getElementById('something'));
...calling this function...
function example(o) {/* A little help here please? */}
...should output the following strings...
window
window.document
document.getElementById('something')
I've attempted to do this with combinations of toString() and eval() among some more miscellaneous shots in the dark without success.
No need insane backwards compatibility, newer ECMAScript / JavaScript features/functions are fine. Feel free to inquire for clarifications though the goal should be pretty straight forward.
This is not possible to do in a self contained script.
If using a preprocessor would be an option, then you could write one which converts example(whatever) into example('whatever'). Other than that I'm afraid you're out of luck.
The first problem is that objects don't have names.
The second problem is that from your examples, you're not really wanting to print the (nonexistent) name of an object, you want to print the expression that evaluated into a reference to an object. That's what you're trying to do in this example:
example(document.getElementById('something'));
For that to print document.getElementById('something'), JavaScript would have had to keep the actual text of that expression somewhere that it would make available to you. But it doesn't do that. It merely evaluates the parsed and compiled expression without reference to the original text of the expression.
If you were willing to quote the argument to example(), then of course it would be trivial:
example( "document.getElementById('something')" );
Obviously in this case you could either print the string directly, or eval() it to get the result of the expression.
OTOH, if you want to try a real hack, here's a trick you could use in some very limited circumstances:
function example( value ) {
var code = arguments.callee.caller.toString();
var match = code.match( /example\s*\(\s*(.*)\s*\)/ );
console.log( match && match[1] );
}
function test() {
var a = (1);
example( document.getElementById('body') );
var b = (2);
}
test();
This will print what you wanted:
document.getElementById('body')
(The assignments to a and b in the test() function are just there to verify that the regular expression isn't picking up too much code.)
But this will fail if there's more than one call to example() in the calling function, or if that call is split across more than one line. Also, arguments.callee.caller has been deprecated for some time but is still supported by most browsers as long as you're not in strict mode. I suppose this hack could be useful for some kind of debugging purposes though.
Don't know why you need this, but you can try walking the object tree recursively and compare its nodes with your argument:
function objectName(x) {
function search(x, context, path) {
if(x === context)
return path;
if(typeof context != "object" || seen.indexOf(context) >= 0)
return;
seen.push(context);
for(var p in context) {
var q = search(x, context[p], path + "." + p);
if(q)
return q;
}
}
var seen = [];
return search(x, window, "window");
}
Example:
console.log(objectName(document.body))
prints for me
window.document.activeElement