Is it possible to know programmatically in runtime whether variable is const in JS?
For example:
const a = {};
const b = 5;
var c = {};
var d = 6;
var e = a;
var f = b;
var g = c;
var h = d;
isConst(a); // true
isConst(b); // true
isConst(c); // false
isConst(d); // false
isConst(e); // false
isConst(f); // false
isConst(g); // false
isConst(h); // false
I don't even know how to approach this issue, assuming I don't want to use external tools (from outside the JS runtime environment), or something like eval().
Maybe it's possible somehow with JavaScript's Proxy() or Reflect()?
If there's no solution that determine if const or not without making changes in the checked code/module/function/block itself, I can maybe accept solutions that require some decorative/wrapping modifications to the code/function.
Maybe something that parse the Function() prototype field that contains the stringified text of the function body?
Maybe we can leverage exceptions here? For example, to catch the exception that raised when you try to manipulate a constant variable, and use it to understand the situation?
As soon as you hand over a primitive value - to a function or another variable - you get this value and not the variable and it's kind of declaration.
To get the wanted, Javascript needs an implementation of each type to check the mutability.
Related
Let's say I have the following code I want to transform using a custom Babel plugin:
let c;
c = document;
console.log(c.readyState);
The goal would be to replace all occurrences of document.readyState with a custom function, e.g. window.getDocumentReadyState().
So the output should look something like this:
let c;
c = document;
console.log(window.getDocumentReadyState());
The difficulty here is to determine which value the object c actually has when the MemberExpression Visitor is called, as I just want to replace MemberExpressions for document. That's why I need to find out if the current value of c is document.
Here's an implementation which just replaces every <obj>.readyState MemberExpression:
/**
* Replace document.readyState with
* window.getDocumentReadyState();
*/
MemberExpression(path) {
const { node, parent } = path;
const objName = node.object.name;
const propName = node.property.name;
if (t.isAssignmentExpression(parent)) {
return;
}
if (t.isCallExpression(parent)) {
const isCallee = parent.callee === node;
if (isCallee) return;
}
if (propName === 'readyState') {
const customReadyStateFn = t.callExpression(
t.memberExpression(
t.identifier('window'),
t.identifier('getDocumentReadyState'),
),
[t.identifier(objName)],
);
path.replaceWith(customReadyStateFn);
}
},
Using this implementation, I perform a runtime check to determine if the object is of type document (HTMLDocument) inside window.getDocumentReadyState, because I wasn't able to do the same using Babel.
But there must be a way to reliably tell if this variable has the value document using static analysis, right?
Essentially, I need to find the last AssignmentExpression of this variable in the current scope.
I already tried looking the variable up in the scope using path.scope.getBinding(<variableName>), but the problem is that the last AssignmentExpression doesn't show up in binding.references. If the value was assigned during declaration (let c = document), it would be no problem, because that reference can be accessed using the binding.
I also tried traversing through the scope, but the AssignmentExpression visitor wasn't invoked.
I am fairly new to Babel and ASTs and reached a point where I don't know what to do next, and I would really like to get rid of that runtime check.
How would you solve such a problem?
Maybe a bit late, but I think it can be done.
You need to traverse through all left-side Identifiers and check if their right-side value is document. While doing so you keep an array with all the variable names that value is equal to document, and delete them from the array if their value changes and it is not document anymore.
Then when you traverse the MemberExpression check if the object name is in the array.
WARNING: As other have stated here, this problem is based upon an inflexible customer requirement. While the question is valid, you should definitely use the simpler solutions (ex: putting your settings in a single object) if possible!
I have an array of variables I want to be "watching out" for. That is: if they were previously defined in the code, I need to be able operate on their values.
var a = 'foo'; // only a gets defined
// ...more code here... IMPORTANT: I can only control what comes BELOW this line!
var target = 'exmple.com/?';
var gets = ['a', 'b', 'c']; // these are the names of the variables I want to check for
gets.forEach(function(element) { // checking each one
if(typeof element !== 'undefined') { // if it's previously defined
target += "&"+element+"="+eval(element); // add `&a=foo` to target
}
});
alert(target);
I'd like the alert to be printing example.com/?&a=foo
I've seen the tons of other answers not to use the eval function, and I have tried window[element] in place without success. How would it be best to do this without having to write an if/else for every variable I want to check?
Assuming you can't do otherwise and everything lives in the global scope, you could do without resorting to using eval(). (I'm pretty sure there must be some valid use cases for eval but I don't think this is one is one of them.)
Since you shouldn't trust the content of these variables you shouldn't be using eval. What if your customer options get compromised? (Or simply your customer doesn't know what they're doing.)
var css_color = 'tweet(document.cookie)';
You can simply leverage the fact that the variables will be accessible from the global scope:
const serialize_options = (...vars) =>
vars
.filter(v => typeof this[v] !== 'undefined')
.map(v => `${v}=${this[v]}`)
.join('&');
console.log(serialize_options('a'));
console.log(serialize_options('a', 'b'));
console.log(serialize_options('a', 'b', 'c'));
console.log(serialize_options('a', 'x', 'y'));
<script>
var a = '10';
var b = '20';
var c = '30';
</script>
The answer to this kind of question is almost always "You're thinking about the problem wrong.", as is the case here.
You make the assumption that this should be done with variables and that backs you into the corner of only being able to use eval() to get out of that corner. I understand that the customer may have asked for regular variables, but if I were a house builder and the customer asked me to build a house out of marshmallow, I'd say "no", that's not the way to do it.
The solution is to use an object with keys and values from the start, which allows you to use array indexing syntax later, instead of eval.
// Keep the items you need to check in an object
var obj = {
a:'foo' // only a gets defined
}
var target = 'exmple.com/?';
var gets = ['a', 'b', 'c'];
// Check each array element
gets.forEach(function(element) {
// Objects can use array indexing syntax where a string is passed as the index
// because objects actually have keys, rather than numeric indexes
if(obj[element]) { // if it's previously defined
target += "&" + element + "=" + obj[element]; // add `&a=foo` to target
}
});
alert(target);
I am interested in the Wasabi tool.
Please have a look on this piece of code.
I don't understand this instruction:
var W = window.Wasabi = window.Wasabi || {};
My final purpose is to adjust a CSS property, dynamically loaded by the JavaScript...
Thanks for the help.
Two things going on here.
Logical OR operator:
a||b will return a if a is truthy or b otherwise.
so window.Wasabi||{} mean return window.Wasabi if it is defined or otherwise return a new empty object.
Double assignment:
c=d=1 set d equal to 1 and c equal to d. i.e. set c and d to 1.
Putting these two things together:
if window.Wasabi is set already then just get a reference to it and store that in W. Otherwise create a new object, store a reference in window.Wasabi (to use next time) and call it W for use now.
If window.Wasabi doesn't exist, then window.Wasabi will be equal to {}. If it already exist, then don't change it. Finally, assign W to window.Wasabi. So, it would be like this code:
var W;
if (!window.Wasabi) {
window.Wasabi = {};
}
W = window.Wasabi;
There are 2 not-so-obvious things in this statement. There's the 2 consecutive equals, and the || at the end. They are not related so Ill tackle them one by one.
var W=window.Wasabi=window.Wasabi;
//Shorthand for
var window.Wasabi=window.Wasabi;
var W=window.Wasabi;
In english, that'd be: W equals window.Wasabi which equals window.Wasabi.
Now, the || is an OR in an operation, it states to take the left most value, if it's a truthy value, OR the one on the right.
var window.Wasabi=window.Wasabi||{};
//equates to
var window.Wasabi;
if(window.Wasabi){
window.Wasabi=window.Wasabi;
}else{
window.Wasabi={};
}
This pattern is very useful to give a default value to a variable. This will answer that Wasabi is at least an object...
There are two main javascript concepts to understand in this statement.
The first one is :
var x = y || {}
This is equivalent to :
if (y)
x = y
else {
x = {}
If y is truethy, then its value will be assigned to x. Else, x will be equal to {} (an empty object)
The second one is :
a = b = "something"
b will be assigned the value "something" and a will be assigned the value of b (which was just set to "something")
Both a and b will be assigned the value "something".
Application to your example :
var W=window.Wasabi=window.Wasabi||{};
If window.Wasabi is not defined (or is falsy), then it will be assigned to {}.
Then the W variable will be assigned the value of window.Wasabi.
Is window.Wasabi is defined, then it will be assigned to the value of itself (nothing will happen), and W will be assigned the value of window.Wasabi.
It's a pretty dirty way to set the initial value of an object if it doesn't exist, but... it works.
I have a var containing a JSON string. (Here I won't parse it because it is not the main problem)
var variable = '{"name":"Johnny Appleseed"}';
var string = "variable";
I evaluate the string.
var evaluatedstring = eval(string);
Now I would like to delete the value of the var variable.
variable = undefined;
If I do:
console.log(evaluatedstring)
It works fine and returns me the JSON string as:
{"name":"Johnny Appleseed"}
But if I do:
evaluatedstring = undefined;
Of course it doesn't work.
eval(string) = undefined;
doesn't work either. (Returns the error Uncaught ReferenceError: Invalid left-hand side in assignment)
How can I delete the content that via the evaluated var?
No jQuery, please.
On Chrome 50+ so not a problem with the browser.
If you're working in the browser: Global variables in JavaScript are created as properties of the window object. Therefore, what you're trying to do can be done like this:
var variable = 'some variable';
var variableName = 'variable';
window[variableName] = undefined;
variable === undefined // Will now be true
However, just the fact that this is possible doesn't mean it's a good idea - in fact, it's just the opposite, it's bad style and will probably confuse anyone looking at your program to no end. If this is really necessary for your algorithm to work, you might want to rethink your data architecture. If this is just a scoping issue (you don't want that variable to pollute your global namespace), an IIFE will likely solve your problem.
I'm starting to play around with Node.js and specifically with the LessCSS compiler. I saw this call on the source code of the lessc script:
new(less.Parser) ({
paths: [path.dirname(input)].concat(options.paths),
optimization: options.optimization,
filename: input,
strictImports: options.strictImports,
dumpLineNumbers: options.dumpLineNumbers
}).parse(data, function (err, tree) {
//...
});
What I don't understand is how that new(lessParser)({}) call works.
From what I've been reading about Object-oriented Javascript I only found examples of new being called like so: object = new Someting
So the questions are: how does new work in that case? Is it calling less.Parser as the constructor? Also, what are the parenthesis (following the new call) executing/returning?
Thanks!
The code you posted is exactly technically (or more or less) the same as doing
var parserOptions = {
paths: [path.dirname(input)].concat(options.paths),
optimization: options.optimization,
filename: input,
strictImports: options.strictImports,
dumpLineNumbers: options.dumpLineNumbers
};
var parser = new less.Parser(parserOptions);
parser.parse(data, function (err, tree) {
//...
});
But with a few shortcuts and with some parenthesis in "unconventional" places.
JavaScript is a loose type language and it is interpreted in tokens. Which means that it is not too limiting on it's syntax, as long as the grammar is respected.
This let things like :
var a = false;
var b = function() { };
var c = false;
new(a || b || c)();
be completely valid, and will create a new instance of b as it is the first one non-false value defined after the tested variable a. (Note that it does not check if b is actually a function. If b was non-false and not a function, the JS engine would throw that it is not a function at run-time.)
Just a comment really.
The expression new(less.Parser)({…}) is identical to new less.Parser({…}).
The parenthesis are a grouping operator that says "evaluate this expression as a single expression". It is common to use that syntax when the expression in the parenthesis would return a different result without the parenthesis, e.g.
var x, y = 7;
alert( typeof (x || y)) // "number"
alert( typeof x || y ) // "undefined"
But since they make no difference to the outcome in the OP, they are pointless and possibly harmful as they change a well known pattern into one that might appear to some as if new can be called as a function.