Example JSFiddle so you can get a better idea of what is going on http://jsfiddle.net/brsXL/3/ (open your console and view the logged vars object).
I am building a parser and computer for a specific subset of maths in JavaScript, it takes an expression as a string from the user and allows them to use variables. To keep my computational logic simple but allow for the use of variables I have created an object that acts like a number but has the bonus of being passed by reference.
var Variable = function(value) {
this.value = value || null;
}
Variable.prototype.valueOf = function() {
return this.value;
}
This works so that:
var a = new Variable(10);
console.log(a + 2); // = 12
console.log(a / 2); // = 5
However as soon as I wish to perform any form of assignment operation such as += the object is lost and gets replaced by the result of the operation against the object's value property. e.g.
var a = new Variable(10);
console.log(a += 2); // = 12
console.log(a); // = 12
The reason I need it to work like this is because I want to use the same function to handle both numbers and variables. I could add code to each assignment operation but this feels sub-optimal to me, e.g.
var ops = {
"+=" : function(a, b) {
if (a instanceof Variable) {
a.value += b;
} else {
a += b;
}
return a;
},
...
}
But I'd much rather write:
var ops = {
"+=" : function(a, b) {
return a += b;
},
...
}
Can this be done?
I'd much rather write:
function(a, b) {
return a += b;
}
Can this be done?
No. It's impossible to pass a Reference value as a single variable. You always will need to use object properties. a is always local-scoped in your function, so changing it won't affect the outer world. And I'd discourage you from trying to make your operators functions that operate on higher-scope variables…
I think in your case it's quite fine to use an explicit test for variables, because the assignment operator has to do that actually. You cannot assign to literals or other values, only to variables. It might even be
var ops = {
"=" : function(a, b) {
if (a instanceof Variable) {
a.value = +b; // cast b to a number (from whatever it is)
return a;
} else {
throw new Error("Invalid assignment to non-variable "+a);
}
},
...
}
Also, to avoid code duplication you might not write out all the compound assignment operators. Define them in a generic way:
["+", "-", "*", "/"].forEach(function(op) {
ops[op+"="] = function(a, b) {
return ops["="].call(this, a, ops[op].call(this, a, b));
};
});
(Updated jsfiddle demo)
Related
I have a JS object I would like to save in Local Storage for future use, and I cannot parse it to a string.
Code:
JSON.stringify({
a: 5,
b: function (param) {
return param;
}
})
Result:
"{"a":5}"
How do I save it for future use, if not with JSON?
(And creating my own Lexer-Parser to interupt string function I dont think is an option)
I'd recommend this approach:
Store arguments and the body in your json:
{"function":{"arguments":"a,b,c","body":"return a*b+c;"}}
Now parse json and instantiate the function:
var f = new Function(function.arguments, function.body);
I think it's save
Usually a question like this indicates an X/Y problem: You need to do X, you think Y will help you do that, so you try to do Y, can't, and ask how to do Y. It would frequently be more useful to ask how to do X instead.
But answering the question asked: You could use replacer and reviver functions to convert the function to a string (during stringify) and back into a function (during parse) to store a string version of the function, but there are all sorts of issues with doing that, not least that the scope in which the function is defined may well matter to the function. (It doesn't matter to the function you've shown in the question, but I assume that's not really representative.) And converting a string from local storage into code you may run means that you are trusting that the local storage content hasn't been corrupted in a malicious way. Granted it's not likely unless the page is already vulnerable to XSS attacks, but it's an issue to keep in mind.
Here's an example, but I don't recommend it unless other options have been exhausted, not least because it uses eval, which (like its close cousin new Function)) can be a vector for malicious code:
// The object
var obj = {
a: 5,
b: function (param) {
return param;
}
};
// Convert to JSON using a replacer function to output
// the string version of a function with /Function(
// in front and )/ at the end.
var json = JSON.stringify(obj, function(key, value) {
if (typeof value === "function") {
return "/Function(" + value.toString() + ")/";
}
return value;
});
// Convert to an object using a reviver function that
// recognizes the /Function(...)/ value and converts it
// into a function via -shudder- `eval`.
var obj2 = JSON.parse(json, function(key, value) {
if (typeof value === "string" &&
value.startsWith("/Function(") &&
value.endsWith(")/")) {
value = value.substring(10, value.length - 2);
return (0, eval)("(" + value + ")");
}
return value;
});
document.body.innerHTML = obj2.b(42);
The construct (0, eval)("(" + value + ")"); ensures that eval runs at global scope rather than within the scope of the reviver function. Normally eval has a magic ability to use the scope you call it in, but that only works when you call it directly. Indirect eval as shown (or just var e = eval; e("(" + value + ")");) doesn't have that magic ability, it runs at global scope.
You can't store functions in JSON.
The value in JSON may contain only string, number, object, array, true, false or null:
Check out it on JSON site.
One simple way of doing this is
var dstr = JSON.stringify( { a: 5
, b: x => x
}
, (k,v) => typeof v === "function" ? "" + v : v
);
I've taken to storing the function name, along with the parameter values, in an array, with the first item in the array being the function name prepended with a $, to separate them from normal arrays.
{
"object": {
"your-function": ["$functionName", "param-1", "param-2"],
"color": ["$getColor", "brand", "brand-2"],
"normal-array": ["normal", "array"]
...
}
}
In the above example I have Sass and JS functions to retrieve color values from a global map/object. Parsing the function in this manner naturally requires custom code, but in terms of "storing" functions in JSON, I like this way of doing it.
I have created JSON.parseIt() and JSON.stringifyIt() functions based on the first answer without using eval
JSON.stringifyIt = (obj)=>{
return(
JSON.stringify(obj, function(key, value) {
if (typeof value === "function") {
return "/Function(" + value.toString() + ")/";
}
if(typeof value === "string"){
return "/String(" + value.toString() + ")/"
}
return value;
})
)
}
JSON.parseIt=(json)=>{
return(
JSON.parse(json, function(key, value) {
if (typeof value === "string" &&
value.startsWith("/Function(") &&
value.endsWith(")/")) {
value = value.substring(10, value.length - 2);
var string = value.slice(value.indexOf("(") + 1, value.indexOf(")"));
if(/\S+/g.test(string)){
return (new Function(string,value.slice(value.indexOf("{") + 1, value.lastIndexOf("}"))))
}else{
return (new Function(value.slice(value.indexOf("{") + 1, value.lastIndexOf("}"))));
}
}
if (typeof value === "string" &&
value.startsWith("/String(") &&
value.endsWith(")/")){
value = value.substring(8, value.length - 2);
}
return value;
})
)
}
// DEMO
var obj = {
string:"a string",
number:10,
func:()=>{
console.log("this is a string from a parsed json function");
},
secFunc:(none,ntwo)=>{console.log(none + ntwo)} ,
confuse:"/Function(hello)/"
}
const stringifiedObj = JSON.stringifyIt(obj);
console.log("the stringified object is: ",stringifiedObj);
const parsedObj = JSON.parseIt(stringifiedObj);
// console.log("the parsed object is: ",parsedObj);
console.log(parsedObj.string);
console.log(parsedObj.number);
console.log(parsedObj.confuse);
parsedObj.func();
parsedObj.secFunc(5,6);
The problems I fixed were
Removed eval.
there was a problem in the stringifying and parsing that if I give a string like
"/Function(hello)/" will be a function when parsed
Made it to two functions
Added parameter insertation
For someone that still need include, for whatever reason, the function definition in JSON, this code can help (but can be slow depending object size):
function Object2JsonWithFunctions(o, space = null) {
var functionList = {}
var fnSeq = 0;
var snrepl = function(k,v){
if(typeof v === 'function'){
fnSeq++;
var funcName = `___fun${fnSeq}___`;
var funcText = ''+v;
functionList[funcName] = funcText
return funcName;
}
return v;
}
var RawJson = JSON.stringify(o, snrepl, space);
for(func in functionList){
var PropValue = `"${func}"`;
RawJson = RawJson.replace(PropValue, functionList[func])
}
return RawJson;}
The code will do the normal convert to JSON.
For functions, the original stringify will return as "prop":"function()..." (function as a string)... The code above will create a placeholder (e.g: "prop":"fn1") and create a function list... After, will replace every placeholder to original function body...
I'm learning how to build functions and my task is to create a function declaration that will return the parameter of greater value. So far, I've only learned how to sums and products -- in other words, I don't know how to get JavaScript to "decide" which parameter is greater.
The extact task description is as follows: Build a function declaration called maxOf2 that takes in two numbers and returns the greater value. Be careful to think about the possibility of equality as well and return one of the numbers.
I've read about Math.max(), but as the course hasn't covered that, I'm not supposed to use it.
Here is what I have so far, which is not much:
function maxOf2(a, b) {
var a = 12;
var b = 4;
return ...;
}
Very basic example:
function maxOf(a, b){
if(a > b){
return a;
} else {
return b;
}
}
You can use that Math.max() function like so:
Math.max(a, b);
And another example that uses ternary operator:
function maxOf(a, b){
return a > b ? a : b;
}
Another answer because there are only 3 answers:
const maxOf = (a,b) => (a > b ? a : b);
I have code that's meant to take an input string, find the name of an operation (add, subtract, multiply, or divide) and do the indicated operation on the numbers also included in the input string. So if my input looks like this
1 a 2 b 3 c 4 d subtract
I want my program to find the word "subtract" and then subtract 2 from 1, 3 from the result and then 4 from the result of that. So far, I have my operations defined as global variables at the top of my program like this:
var operations = {
add: function(a, b) {
"use strict";
return Number(a) + Number(b);
},
subtract: function(a, b) {
"use strict";
return a - b;
},
multiply: function(a, b) {
"use strict";
return a * b;
},
divide: function(a, b) {
"use strict";
return a / b;
}
};
The area I'm having problems with is the while loops I'm trying to make in order to recognize the first instance of one of those operation names occurring in my input string, it looks like this:
function doArithment() {
"use strict";
var i = 0;
clearResults();
sepNsLs();
var found = false;
var q = 0;
var keys = Object.keys(operations);
while (q < keys.length) {
if (arrayses.letteros.indexOf(keys) !== -1) {
found = true;
break;
} else if (arrayses.letteros.indexOf(keys) === -1) {
q += 1;
}
}
if (found) {
var result = arrayses.numeros[0];
while (i < arrayses.numeros.length) {
if (i === 0) {
result = arrayses.numeros[0];
} else {
result = keys[q](result, arrayses.numeros[i]);
}
i += 1;
}
clearResults and sepNsLs should not have any bearing on whether or not this loop works properly, but if it's asked for, I can provide the code for both of those functions. arrayses.letteros is the array of non-numbers from my input string, and that's where I try to look for the operation names, arrayses.numeros is the array in which I put the numbers from my input string. I had a for-in statement working completely in this function, but JSLint doesn't like that, so I've been trying to make this work in other ways. Is there something I'm missing here?
You are searching for the entire keys array. You need to search for the individual element. Anywhere you are referencing keys you mean to be referencing keys[q].
arrayses.letteros.indexOf(keys[q])
I have a class, it has two methods: get(), set().
function A() {
var value = '';
this.get = function () {
return value;
};
this.set = function (v) {
value = v;
};
}
And I have a function f():
/**
*
* #param { String | Number } b
*/
function f(b){
var value;
if(b instanceof A){
value = b.get();
}else{
value = b;
}
}
I could create an object:
var test = new A();
test.set('Hello');
f(test); //Hello
f(10); //10
The instanceof operator tests whether an object has in its prototype chain the prototype property of a constructor.
I heard it is a bad practice use this operator.
The question is: Is it imposible to get rid of instanceof operator from my code?
Maybe I should use force type conversion or use another getter, setter?
<--UPDATE-->>
I found simple solution, I could coerce b.get to boolean type and check it. It works, but maybe it has incidental effect.
value = (!!b.get) ? b.get(): b;
<--UPDATE 2-->>
Another way: value = (b.constructor = A) ? b.get(): b;
It's not the instanceof operator itself which is the problem, it's what you're using it for. Your function is expecting values of two different types: either an instance of A which needs to be handled a certain way or anything else which can be used as is.
The problem with this is a) why is your function allowing two different types to begin with and couldn't you harmonise that into just one type, and b) if it's accepting "anything" or "A", why A specifically and not something more general?
Assuming that you cannot reasonably change point a), you can at least make b) better:
if (typeof b.get == 'function') {
value = b.get();
}
You've just made your function a little more flexible and adaptable to future change by testing for the actual thing you're interested in: a get method. That b is an instanceof A is sort of irrelevant for the task at hand and may limit you in the future.
How do I compare 2 functions in javascript?
I am not talking about internal reference. Say
var a = function(){return 1;};
var b = function(){return 1;};
Is it possible to compare a and b ?
var a = b = function( c ){ return c; };
//here, you can use a === b because they're pointing to the same memory and they're the same type
var a = function( c ){ return c; },
b = function( c ){ return c; };
//here you can use that byte-saver Andy E used (which is implicitly converting the function to it's body's text as a String),
''+a == ''+b.
//this is the gist of what is happening behind the scences:
a.toString( ) == b.toString( )
Closures mean that you need to be very careful what you mean when you say "compare". For example:
function closure( v ) { return function(){return v} };
a = closure('a'); b = closure('b');
[a(), b()]; // ["a", "b"]
// Now, are a and b the same function?
// In one sense they're the same:
a.toString() === b.toString(); // true, the same code
// In another sense they're different:
a() === b(); // false, different answers
// In a third sense even that's not enough:
a2 = closure('a');
a() === a2(); // true
a === a2; // false, not the same object
The ability to reach outside the function means that in a general sense, comparing functions is impossible.
However, in a practical sense you can get a very long way with Javascript parsing libraries like Esprima or Acorn. These let you build up an "Abstract Syntax Tree" (AST), which is a JSON description of your program. For example, the ast your return 1 functions looks like this
ast = acorn.parse('return 1', {allowReturnOutsideFunction:true});
console.log( JSON.stringify(ast), null, 2)
{
"body": [
{
"argument": {
"value": 1, // <- the 1 in 'return 1'
"raw": "1",
"type": "Literal"
},
"type": "ReturnStatement" // <- the 'return' in 'return 1'
}
],
"type": "Program"
}
// Elided for clarity - you don't care about source positions
The AST has all the information you need to make comparisons - it is the Javascript function in data form. You could normalize variable names, check for closures, ignore dates and so on depending on your needs.
There are a bunch of tools and libraries to help simplify the process but even so, it's likely to be a lot of work and probably not practical, but it is mostly possible.
You can compare two variables that might contain function references to see if they refer to the exact same function, but you cannot really compare two separate functions to see if they do the same thing.
For example, you can do this:
function foo() {
return 1;
}
var a = foo;
var b = foo;
a == b; // true
But, you can't reliably do this:
function foo1() {
return 1;
}
function foo2() {
return 1;
}
var a = foo1;
var b = foo2;
a == b; // false
You can see this second one here: http://jsfiddle.net/jfriend00/SdKsu/
There are some circumstances where you can use the .toString() operator on functions, but that's comparing a literal string conversion of your function to one another which, if even off by a teeny bit that is inconsequential to what it actually produces, will not work. I can think of no situation where I would recommend this as a reliable comparison mechanism. If you were seriously thinking about doing it this way, I'd ask why? What are you really trying to accomplish and try to find a more robust way of solving the problem.
toString() on a function returns the exact declaration. You can modify jfriend00's code to test it out.
This means you can test to see if your functions are exactly the same, including what spaces and newlines you put in it.
But first you have to eliminate the difference in their names.
function foo1() {
return 1;
}
function foo2() {
return 1;
}
//Get a string of the function declaration exactly as it was written.
var a = foo1.toString();
var b = foo2.toString();
//Cut out everything before the curly brace.
a = a.substring(a.indexOf("{"));
b = b.substring(b.indexOf("{"));
//a and b are now this string:
//"{
// return 1;
//}"
alert(a == b); //true.
As the others said, this is unreliable because a single whitespace of difference makes the comparison false.
But what if you're employing it as a protective measure? ("Has someone altered my function since I created it?") You may actually desire this kind of strict comparison then.
ES6+ clean solution using template literals:
const fn1 = () => {}
const fn2 = () => {}
console.log(`${fn1}` === `${fn2}`) // true
Basically means:
console.log(fn1.toString() === fn2.toString()) // true
Convert function to string, then, replace line-break and space before comparing:
let a = function () {
return 1
};
let b = function () {
return 1
};
a = a.toString().replace(/\n/g, '').replace(/\s{2}/g, ' ');
b = b.toString().replace(/\n/g, '').replace(/\s{2}/g, ' ');
console.log(a); // 'function () { return 1}'
console.log(b); // 'function () { return 1}'
console.log(a === b); // true
b = function () {
return 2
};
b = b.toString().replace(/\n/g, '').replace(/\s{2}/g, ' ');
console.log(b); // 'function () { return 2}'
console.log(a === b); // false
b = () => 3;
b = b.toString().replace(/\n/g, '').replace(/\s{2}/g, ' ');
console.log(b); // '() => 3'
console.log(a === b); // false
p/s: If you are using ES6, try to use let instead of var.