blah('A');
function blah(letter){
arrayA.push('something');
}
I want to push something to an array where the name of the array is 'array' plus a letter being passed to it.
I can console out 'arrayA' fine:
console.log('array'+${letter})
But if I try to build the array name, the same logic doesn't work:
array${letter}.push('something')
In the browser (where the global objects, functions, and variables become members of the window object) you can create and access dynamically named objects using the bracket notation.
Were you looking for something like this?
function blah(letter){
window['array' + letter] = [];
window['array' + letter].push('something');
}
blah('A');
After this you can access and use the newly created array (arrayA) as usual.
arrayA.push('something else');
In node you can probably achieve this using global instead of window.
Try it and forget it (or replace the 3 occurrences of window with global for testing with node.js):
function test(name,value){
if(!window["array"+name])
window["array"+name]=[];
window["array"+name].push(value);
}
try{console.log(arrayA);}catch(e){console.log("arrayA missing: "+e);}
test("A",10);
try{console.log(arrayA);}catch(e){console.log("arrayA missing: "+e);}
test("A",20);
try{console.log(arrayA);}catch(e){console.log("arrayA missing: "+e);}
window is the global scope in a browser, and generally you should not rely on global variables without a good reason. They lack context (that is why they are 'global'), making it hard to tell where they belong, what they are and where they come from. That is something what most programming paradigms advise against.
The thing also works with node.js, just it has global as global context, you can paste this snippet into https://www.tutorialspoint.com/execute_nodejs_online.php as a test, replace the 3 window-s, and it will work (you can of course wrap it into a proper module too, just that is more work). What is written above against the usage of global variables stays true for node.js too. Do not use the global context especially if you are developing modules.
However, instead of window, the syntax works with any object too, and that would be considered okay:
var obj={};
console.log(obj.something);
obj['some'+'thing']=10;
console.log(obj.something);
So you can freely have your own 'context' object (if you write the var obj={}; line in the top-level of a module, it will be available everywhere in that module, and it will not interfere with the outside world), and create/access its members using this array-like syntax (obj['something']), constructing the names on the fly when necessary.
Related
Please consider this snippet:
let variableName = 'internalVariable';
{
let internalVariable = 'whatever';
console.log(eval(propertyKey)); // prints 'whatever'
}
What are my options to accessing internalVariable via the string stored in variableName? I was hoping for something like scope[variableName], but there seems to be nothing like it.
Given this particular scenario, are there any alternatives to using eval?
In situations where dynamic access to something like a variable is required, the idiomatic thing to do is employ an object and dynamically compute property names as appropriate.
var obj = {};
obj[getPropertyName()] = "hello world";
Variables declared in functions with var, let, or const do exist as properties of something like an object (the closure of a function call), but JavaScript does not provide any way of referring to that thing as an object. Using eval() is possible, but generally it's a bad idea because runtime optimization is not attempted in modern runtime systems because eval() makes that intractably complicated.
The only way a variable is aliased in JavaScript is via the arguments object, and that's sufficiently weird that it's explicitly discouraged in "strict" mode.
[edit] — in re: Bergi's comment below, the (generally deprecated) with statement allows implicit references to object properties, and the export mechanism for modules can create aliases, though to me it's hard to imagine that being a good thing in actual practice.
In Javascript, local variables do not live on any object that I'm aware of. That is,
function foo() {
const x = 2;
self.x; // undefined
this.x; // undefined
window.x; // undefined
x; // 2, obviously
eval('x'); // 2
}
The last option eval('x') shows that it is possible to refer to these variables by name. I'm looking to extend this and access a variable using a name pattern:
function foo() {
// Code not under my direct control
function foobar_abc() {}
function other_functions() {}
// Code under my control
const matchingFunction = // find the function with name matching 'foobar_*'
}
If this lived on an object, I would use something like myObject[Object.keys(myObject).find((key) => key.startsWith('foobar_'))]. If it were in the global scope, myObject would be window and everything works.
The fact that eval is able to access the variable by name implies that the value is available somewhere. Is there any way to find this variable? Or must I resort to techniques which re-write the (potentially very complex) code which is not under my direct control?
I'm targeting modern browsers, and in this use case I don't mind using eval or similarly hacky solutions. Arbitrary code is already being executed, because the execution of user-provided code is the purpose.
Another option is to use code parsing to deduce the function names using a javascript AST (abstract syntax tree) library. The "esprima" package will probably be good place to look:
https://www.npmjs.com/package/esprima
So you can do
import esprima from 'esprima'
const userCodeStructure = esprima.parseScript( userProvidedJavascriptString );
const allTopLevelFunctionDeclarations = userCodeStructure.body.filter( declaration => declaration.type === "FunctionDeclaration" );
const allTopLevelFunctionNames = allTopLevelFunctionDeclarations.map( d => d.id );
I haven't tried this myself by the documentation suggests it should work (or something like it):
http://esprima.readthedocs.io/en/latest/
One possible approach that might help you here is to evaluate at global scope rather than in a function, and that might put the functions on the window object instead of in local scope.
Easiest way to do this is probably to write a new tag into the document and inject the user-provided code.
Relying on variable names is the wrong approach.
eval is evil. It may not be available under CSP. Considering that the code is supposed to run in browser, the biggest problem is that variables don't have expected names in minified code. They are a, b, c...
In order to maintain their names, they should be object properties - and so they will be available on the object.
Or must I resort to techniques which re-write the (potentially very complex) code
This is refactoring and that's what should be done to avoid bad code that smells and creates major problems.
Context: I have written a mini JS library for myself which is simply a collection of commonly used classes. I have followed the IIFE (http://benalman.com/news/2010/11/immediately-invoked-function-expression/) technique to separate my code into modules/classes and they're all grouped under a common global namespace var. Let's call it, ns. So we have a typical setup, ns.ClassA, ns.ClassB, etc. Now on the other hand, a separate script (main.js) is loaded at runtime and appended to the document, and this main.js contains the actual code that uses these classes.
Goal: I am trying to find an elegant way of accessing the classes inside main.js directly by calling the class name instead of having to access them through ns. . For example, I would want to be able to do var a = new ClassA(); instead of var a = new ns.ClassA();
Solutions researched & considered:
1) the dreaded 'with' keyword (javascript "static imports"). In this case, I would do something like with(ns){ var a = new ClassA()} , except I will have to wrap the entire main.js inside the with(ns) statement. This is undesirable for obvious reasons.
2) using locally declared variables.
var ClassA = ns.ClassA, ClassB = ns.ClassB;
and then, I will be able to instantiate ClassA and ClassB directly. However, this approach would require me to manually maintain the declaration, and it will just get very messy and hard to maintain as the number of classes increase in the package.
3) pollute the global scope by injection
use a for loop to iterate over ns and map all the classes inside nsto global scope. This is clearly undesirable, and also it will create conflicts for cases such as ns.Event, ns.Audio etc.
4) PaperScript-style (http://paperjs.org/tutorials/getting-started/working-with-paper-js/)
Inspired by PaperScript from PaperJS, where PaperScript code is automatically executed in its own scope that without polluting with the global scope still has access to all the global browser objects and functions, such as document or window. Looking at their source code on GitHub (sorry SO won't let me post any more links), they seem to be using some custom script pre-processing and then Acorn.js to parse. The end result is that one can directly refer to any class inside the paper object. For example, var path = new Path() instead of var path = new paper.Path(), which is exactly what I wanted to achieve. However, my fear is that it might seem to be too much work to implement such a simple feature. So I wanted to see if any one has any ideas?
Thank you for taking your time to read this verbose description of the problem. Any inputs are appreciated.
Note: I have done my best in the past two days into researching this topic, please forgive me if I missed any obvious solutions. In addition, if there's no easy solution to this, I will simply stick with the ns.ClassA pattern. Thank you for your help!
I'm not an expert but I believe you could create a prototype of String and set your vars like so
String.prototype.ns = function(){
return new ns[this]();
}
var ca = "ClassA".ns();
I'm exploring a variety of options for a JavaScript routing framework that I'm working on, and I'd like to provide a DSL written in JavaScript for defining the router.
I had the idea of using temporary prototype overrides on the String class (maintain a hash of the previous prototype values, override, run the DSL code, reset the prototype values to what they were) to all for something like this:
DSL.run(function() {
"hello".isSomething();
"foo".isSomethingElse();
});
The other idea was to use define temporary global variables and then remove/reset them after the DSL is done running. That way, if you run the DSL closure with window (or whatever the global object is) as the this context, I believe you should be able to do something like:
DSL.run(function() {
defineSomething("hello");
defineSomethingElse("foo");
});
I know I know I know I should be super careful about the prototype overloads and polluting the global namespace, but this seems to be a pretty localized and easily cleanup-able approach to keep that sort of thing from happening. My question is, are there any other considerations that would keep this from being a reality?
One potential problem I could think of is whether this would work in a Node.js setting, where code is stored in separate modules and global variables kept from each other, which I think would eliminate option B, but what about String prototype overloads? Those are shared between modules, right? e.g. if I include module A, which sets String prototype values, those prototype values will be available in the including code, right?
Also, let me know if anyone's done this sort of thing before. I think it's a clever approach to this sort of problem and I haven't seen anything quite like it, but I want to make sure I'm not leaving out something really obvious and damning.
Use delete String.prototype[method].
var dsl = function(f){
var _ = String.prototype;
_.isSomething = function(){
console.log('isSomething: '+this);
}
_.isSomethingElse = function(){
console.log('isSomethingElse: '+this);
}
f();
delete _.isSomething;
delete _.isSomethingElse;
}
dsl(function(){
"hello".isSomething(); // isSomething: hello
"foo".isSomethingElse(); // isSomethingElse: foo
});
// "hello".isSomething(); // error "Object has no method 'isSomething'"
// "foo".isSomethingElse(); // error "Object has no method 'isSomethingElse'"
I'm trying to work out what is considered valid for the property name of a javascript object. For example
var b = {}
b['-^colour'] = "blue"; // Works fine in Firefox, Chrome, Safari
b['colour'] = "green"; // Ditto
alert(b['-^colour']); // Ditto
alert(b.colour); // Ditto
for(prop in b) alert(prop); // Ditto
//alert(b.-^colour); // Fails (expected)
This post details valid javascript variable names, and '-^colour' is clearly not valid (as a variable name). Does the same apply to object property names? Looking at the above I'm trying to work out if
b['-^colour'] is invalid, but works in all browsers by quirk, and I shouldn't trust it to work going forward
b['-^colour'] is completely valid, but it's just of a form that can only be accessed in this manner - (it's supported so Objects can be used as maps perhaps?)
Something else
As an aside, a global variable in javascript might be declared at the top level as
var abc = 0;
but could also be created (as I understand it) with
window['abc'] = 0;
the following works in all the above browsers
window['#£$%'] = "bling!";
alert(window['#£$%']);
Is this valid? It seems to contradict the variable naming rules - or am I not declaring a variable there? What's the difference between a variable and an object property name?
Yes, objects can be used as maps, and any string can be a property name. As you've discovered, some properties can only be accessed using the bracket syntax.
window['abc']
is accessing a property. It is not a variable, even though it refers to the same value (at the global level) as:
abc
Object property naming rules and variable naming rules are separate. The standard only "reserves" a handful of property names (such as prototype and constructor, IIRC), but other than those, any string goes.
Except when the execution environment (i.e. the browser) decides to add more magic properties, of course. (I hear setting __proto__ breaks some things in quite weird ways)
Every time you create a global variable you create in fact a new member of a global object (which is window in browser environment, global in Node.js, etc.). This is why window.x is exactly the same like (global) var x, this.x or just x.
Understanding JavaScript object like a map is quite right, because: a) you can add a new element dynamically - at any moment; b) the element can have any name - also including special characters, c) you can try to access a non-existing element of an object/map and it is not an error, d) you can remove an element from an object.
If you like to access object members with standard dot notation (eg. a.x) you are not allowed to use any special characters different than _ or $; also the name cannot start from a number. For all other cases you are forced to use square brackets and quotation marks to access object elements.