Suppose you have the following:
function myfunc() {
// JS code
}
var args = '{ "strfield": "hello world", "numfield": 10, "funcfield": myfunc }';
The problem: how do you process the args variable before submitting it to the JSON parser so that myfunc is replaced with the result of myfunc.toString() (that is, the body of the function)? The proposed solution should work on arbitrary functions and such quasi-JSON strings.
We use the optional second replacer argument to JSON.stringify to preprocess key/values, emitting functions in stringified form:
function stringify_with_fns(obj) {
return JSON.stringify(obj, function(key, value) {
return typeof value === "function" ? value.toString() : value;
});
}
Then to turn the stringified functions back into real functions on the way out, we use the optional second reviver parameter to JSON.parse, as in
function parse_with_fns(json) {
return JSON.parse(json, function(key, value) {
if (looks_like_a_function_string(value)) {
return make_function_from_string(value);
} else {
return value;
}
});
}
looks_like_a_function_string is just a regexp, and the first cut at make_function_from_string can use eval:
function looks_like_a_function_string(value) {
return /^function.*?\(.*?\)\s*\{.*\}$/.test(value);
}
function make_function_from_string(value) {
return eval(value);
}
To avoid eval, we can pick apart the function string to find its arguments and body, so we can pass them to new Function:
function make_function_from_string(value) {
var args = value
.replace(/\/\/.*$|\/\*[\s\S]*?\*\//mg, '') //strip comments
.match(/\(.*?\)/m)[0] //find argument list
.replace(/^\(|\)$/, '') //remove parens
.match(/[^\s(),]+/g) || [], //find arguments
body = value.match(/\{(.*)\}/)[1] //extract body between curlies
return Function.apply(0, args.concat(body);
}
Testing:
x = parse_with_fns(stringify_with_fns({a: function() {var x=1;}}))
x.a
> function anonymous() {var x=1;}
Note however that since functions created by new Function are all in the global scope, they will lose their enclosing scope and closures.
The only remaining question is whether this is useful. I suppose it might be for small utility functions.
Extending the concept to serializing/deserializing regexps or date objects is left as an exercise.
Like this? I had to change it because what you have isn't a valid JSON string since myfunc doesn't have double quotes. So I added those.
This parses it, gets the value of funcfield, finds the function (although it assumes it is in the global context), calls toString() updating the value of funcfield, the re-stringifies it as JSON.
Example: http://jsfiddle.net/patrick_dw/QUR6Z/
var myfunc = function() {
alert('hi');
};
var args = '{ "strfield": "hello world", "numfield": 10, "funcfield": "myfunc" }';
var parsed = JSON.parse( args );
parsed.funcfield = window[parsed.funcfield].toString();
var stringified = JSON.stringify( parsed );
alert(stringified);
Was this what you meant?
EDIT:
I guess you could use the current context, as long as that context is contained within the context that owns the function.
parsed.funcfield = this[parsed.funcfield].toString();
Or if you can't double quote the function name, you could use eval instead if you're certain the data is safe.
Example: http://jsfiddle.net/patrick_dw/QUR6Z/1/
var args = '{ "strfield": "hello world", "numfield": 10, "funcfield": myfunc }';
window.eval( 'var parsed = ' + args );
parsed.funcfield = parsed.funcfield.toString();
var stringified = JSON.stringify( parsed );
alert(stringified);
Related
I am working on a problem and I am not allowed to use ES6 syntax so I can't use the spread/rest operator. How can I write a function that will take any number of arguments without a spread/rest operator?
function memoize(func) {
var cache = {};
return function (...args) {
if (args in cache) {
return cache[args];
} else {
cache[args] = func(...args);
return func(...args);
}
};
};
Firstly you have to use the arguments object. It is an older feature which makes a variable called arguments available in non arrow functions. The value of arguments is all the arguments a function receive. It is an array-like object, not an array. This eliminates the use of rest operator.
Also, it is better to create a key, when you are creating a generic function.
Why? Javascript object keys can only be string. If an object is used as a key it gets auto converted to a string : [object Object]. So basically all your object keys will override each other. Here is a demo:
const obj1 = { a : 1};
const obj2 = { b : 2};
const x = {};
x[obj1] = 2;
console.log(x);
x[obj2] = 2;
console.log(x);
To generate hash key you could use any method. Below is not a bullet proof implementation but I have just joined all the arguments into a . separated array.
Now we have to call your function with these arguments, in the else condition. How? When you want to forward you arguments to another function, and they are in the form of an array, you can use something like apply. It is pre ES6 feature which lets you run a function in a different context, or lets you pass arguments as an array. Here we won't be changing the context, but the second use case is what we will be using.
function memoize(func) {
var cache = {};
return function () {
const key = Object.values(arguments).join('.');
console.log(arguments);
console.log(cache);
if (key in cache) {
return cache[key];
} else {
cache[key] = func.apply(null,arguments);
return cache[key];
}
};
};
const conc = (str,str2,str3) => `${str}_${str2}_${str3}`;
const memoizedSq = memoize(conc);
memoizedSq('hello','hi','hey');
memoizedSq('bye','see you','so long');
memoizedSq('hello','hi','hey');
I have used join to create the string.
Minor optimization I did by changing your code in the else condition is not calling function multiple times. I just saved it in the object and returned the same value.
Note: This could break for cases where if a function takes a string, and the string itself contains ..
If I have this ES6 function declaration and invocation:
function myFunction (arg1, arg2 = "bob") {
console.log("arguments", arguments);
}
myFunction(1);
...the console.log() statement shows only one argument with a value of "1". "bob" is nowhere to be seen. Is this expected and/or desired behavior? I would expect that default values would be available in the arguments object. If not, is there a way to dynamically get all arguments + defaults in some other manner?
Thanks in advance!
Yes, this is expected and desired. The arguments object is a list of the values that were passed into the function, nothing else.
It is not implicily linked to the parameter variables (that get assigned the default values), like it was in sloppy mode.
Is there a way to dynamically get all arguments + defaults in some other manner?
No. What parameters you have and whether they have default initialisers is static, you don't need to do anything here dynamically. You can do Object.assign([], arguments, [arg1, arg2]) for your example function.
As you know by now, there is no native method to get both "passed arguments AND defaults where arguments are not passed". But there is a workaround:
This function (that I found here) gets all parameters of a given function:
function getArgs(func) {
var args = func.toString().match(/function\s.*?\(([^)]*)\)/)[1];
return args.split(',').map(function(arg) {
return arg.replace(/\/\*.*\*\//, '').trim();
}).filter(function(arg) {
return arg;
});
};
So, combining this function with the arguments of your function myFunction, we can get an array that has what you want:
function myFunction (arg1, arg2 = "bob") {
var thisArguments = arguments;
console.log(getArgs(myFunction, thisArguments));
};
function getArgs(func, argums) {
var args = func.toString().match(/function\s.*?\(([^)]*)\)/)[1];
var argsArray = args.split(',').map(function(arg) {
return arg.replace(/\/\*.*\*\//, '').trim();
}).filter(function(arg) {
return arg;
});
for(var i = 0; i < argsArray.length; i++){
argsArray[i] += " (default)";
}
var defaults = argsArray.slice(argums.length);
argums = Array.prototype.slice.call(argums);
return argums.concat(defaults);
};
Now, we can see the information in the console calling myFunction:
1. Passing more arguments than parameters
This will return only the arguments.
myFunction("foo", "bar", "baz");
//returns: ["foo", "bar", "baz"]
2. Passing less arguments than parameters
Will return the arguments and the remainder parameters as default, as you want (I added "default" to each string).
myFunction("foo");
//returns ["foo", "arg2 = "bob" (default)"]
3. Passing no arguments
This will return all the parameters.
myFunction();
//returns ["arg1 (default)", "arg2 = "bob" (default)"]
This is the fiddle: https://jsfiddle.net/gerardofurtado/25jxrkm8/1/
I've got this format prototype method (simplified) and what I wanna do is check if char is alphabetic (case insensitive), and if it's also a function declared in the same scope. If it is, I want to call the function. So if I pass in x it should execute the alert.
I'm gonna be using this method to format a date by the given format string. E.g. format('H:i:s') it would check if H, i, and s is a function and call them.
How can achieve that?
I tried something based on this answer: https://stackoverflow.com/a/359910/1115367
Here's my code:
function Time() {
//initialization
}
Time.prototype = {
format: function (char) {
if (char.test(/[a-z]/i) && typeof window[char] === 'function') { //undefined
window[char]();
}
function x() {
alert('works');
}
}
};
If I pass in a value it returns: Uncaught TypeError: undefined is not a function
There is no way to retrieve a local variables (or function) by name (as far as I know anyway).
Declared named functions are assigned to a variable of the same name: function thing(){} ~ var thing = function(){}
Global variables are automatically attached to window so you can retrieve them by name using the window object.
However, that is (fortunately) not the case for all other variables.
For example
var global1 = 'thing'; // root level (no wrapping function) -> global
(function(){
global2 = 'global'; // no 'var' keyword -> global variable (bad practice and not accepted in strict mode)
var local = 'whatever'; // local variable
function check(name){
if(window[name]) console.log('found: '+ name);
else console.log('nope: '+name);
}
check('foo');
check('global1');
check('global2');
check('local');
}());
will output:
[Log] nope: foo
[Log] found: global1
[Log] found: global2
[Log] nope: local
What you can do however is to attach your methods to an object.
Edit
If it is an option, you can also directly pass the function as argument (instead of a string containing its name).
function a(){}
function toCall(f){
// do stuff with f
}
toCall(a);
test() is not a function of strings, it is a function of RegExp. See the documentation for test here: RegExp.prototype.test()
So, create a RegExp object first, then use it to call test, passing in your string, char.
After looking at your code more, I think you need to turn turn your format function into a 'class' (JavaScript doesn't really have classes, but you can create an object from a function).
function Formatter() {
this.format = function(char) {
var regex = /[a-z]/i;
if (regex.test(char) && typeof this[char] === 'function') {
this[char]();
}
};
this.x = function() {
alert('works');
}
}
var formatter = new Formatter();
formatter.format('x');
And here is the working jsfiddle.
As you can see, you create a formatter object, which contains the format function and your x function in its scope, this.
You can use an Immediately Invoked Function Expression (IIFE) to create your scope:
var Time = (function() {
var handlers = {
x: function () {
console.log('x invoked');
}
};
function Time() {
// fun stuff
}
Time.prototype = {
format: function(char) {
handlers[char](); // if char === 'x'
}
};
return Time;
}());
I want to programmatically reach a method that is nested inside a object.
var app = {
property:{
method:function(){},
property:"foo"
}
}
Normally you would access it like so: app.property.method
But in my case, In runtime I get a string that I want to interpolate that into calling the method function
Now, how can method be accessed programmatically when i have the following string
"app.property.method"
For a reference please see:
http://jsfiddle.net/adardesign/92AnA/
A while ago I wrote this little script to get an object from the string describing its path:
(function () {
"use strict";
if (!Object.fromPath) {
Object.fromPath = function (context, path) {
var result,
keys,
i;
//if called as `Object.fromPath('foo.bar.baz')`,
//assume `window` as context
if (arguments.length < 2) {
path = context;
context = window;
}
//start at the `context` object
result = context;
//break the path on `.` characters
keys = String(path).split('.');
//`!= null` is being used to break out of the loop
//if `null` or `undefined are found
for (i = 0; i < keys.length && result != null; i+= 1) {
//iterate down the path, getting the next part
//of the path each iteration
result = result[keys[i]];
}
//return the object as described by the path,
//or null or undefined if they occur anywhere in the path
return result;
};
}
}());
You'd need to use bracket notation (i'd avoid the other option - eval()). If the app variable is global, then it would be a property of the window object:
executeFunctionByName("app.property.method", window);
Method borrowed from: How to execute a JavaScript function when I have its name as a string
The method essentially just breaks your window["app.property.method"] (which would fail) into window["app"]["property"]["method"] (which works).
You could try this:
var methodString = "app.property.method";
var method = eval(methodString);
Then method will be a function pointer which may be called like so:
method();
Say I've got a Javascript string like the following
var fnStr = "function(){blah1;blah2;blah3; }" ;
(This may be from an expression the user has typed in, duly sanitized, or it may be the result of some symbolic computation. It really doesn't matter).
I want to define fn as if the following line was in my code:
var fn = function(){blah1;blah2;blah3; } ;
How do I do that?
The best I've come up with is the following:
var fn = eval("var f = function(){ return "+fnStr+";}; f() ;") ;
This seems to do the trick, even though it uses the dreaded eval(), and uses a slightly convoluted argument. Can I do better? I.e. either not use eval(), or supply it with a simpler argument?
There's also the Function object.
var adder = new Function("a", "b", "return a + b");
You can do this:
//in your case: eval("var fn = " + fnStr);
eval("var fn = function(){ blah1;blah2;blah3; }");
fn();
Not sure how to get it much simpler, sometimes there's no (better) way around eval(). Here's a quick example of this in action.
Use parentheses.
var fn = eval("(function() {...})");
This technique is also good for transmitting JSON values.
By the way, it's often better to build functions by composing them directly from other functions. If you are using strings, you have to worry about things like unexpected variable capture.
Here's what I use for simple cases:
// an example function
function plus(...args) {
return args.reduce( (s,v) => s+v, 0 );
}
// function to string
let str = plus.toString();
// string to function
let copy = new Function('return ' + str)();
// tests
console.assert(plus.name == 'plus');
console.assert(copy.name == 'plus');
console.assert(plus.constructor == Function);
console.assert(copy.constructor == Function);
console.assert(plus(1,2,3,4) === copy(1,2,3,4));
console.assert(plus.toString() === copy.toString());
You can also insert the string into a script element and then insert the script element into the page.
script_ele = window.document.createElement("script");
script_ele.innerHTML = 'function my_function(){alert("hi!");}';
window.document.body.appendChild(script_ele);
my_function();
One way:
var a = 'function f(){ alert(111); } function d(){ alert(222);}';
eval(a);
d();
A second more secure way to convert string to a function:
// function name and parameters to pass
var fnstring = "runMe";
var fnparams = ["aaa", "bbbb", "ccc"];
// find object
var fn = window[fnstring];
// is object a function?
if (typeof fn === "function") fn.apply(null, fnparams);
function runMe(a,b){
alert(b);
}
Look at the working code: http://plnkr.co/edit/OiQAVd9DMV2PfK0NG9vk
The Function constructor creates a new Function object. In JavaScript every function is actually a Function object.
// Create a function that takes two arguments and returns the sum of those arguments
var fun = new Function("a", "b", "return a + b");
// Call the function
fun(2, 6);
Output: 8
You can call Parse your string as javascript fuction
function getDate(){alert('done')} // suppose this is your defined function
to call above function getDate() is this in string format like 'getDate()'
var callFunc=new Function('getDate()') //Parse and register your function
callFunc() // Call the function