Is there an option to get a reference to the whole script variables?
lets assume this code:
const a=n=>n+1,
b={};
let c=3
I wonder if I can access all variables by one reference, like this vars:
vars.b //{}
vars[varName]
so that I'll be able to pass it to other sub modules easily instead of creating a manualy associative array with the variables of the script
It is possible only to some limit.
There is globalThis variable acting as a container for all top-level variables.
And also in every function arguments variable is available holding everything passed to function in the current call.
I'm not aware of anything similar for function-/block-scope variables
I think you can assign those variables to a JSON object and then export the JSON object as follows so that you can reference all the variables in other file.
// scripts.js file
let a = n => n+1;
let b = {};
let c = 3;
module.exports = { a, b, c };
// index.js file
const scripts = require('./scripts');
console.log(scripts.a(4));
console.log(scripts.b);
console.log(scripts.c);
No, there's no way to programatically get access to the environment record of a given function/scope/closure. To do something like this, the best way would be to define the variables as part of an object or Map in the first place. Instead of
const a=n=>n+1,
b={};
let c=3
do
const theNamespace = {
a: n=>n+1,
b: {},
c: 3
};
and pass theNamespace around.
Related
look at this example:
const a = 5
module.exports.a = a;
let b = "foo"
module.exporta.b = b
if we export this variables . in everywhere a variable is const and b variable is let .what about this example :
module.exports.c = "bar"
what is this? a var type? let? const? I mean javascript engine treat this to what? I am getting wrong definition or behavior of javascript or this is a correct question that came to my mind?
const and let are for defining variables. Things in module.exports are properties of an object (that object being module.exports), and so they are controlled by their property descriptors. Whether or not the value is mutable is controlled by the writable descriptor field. It no longer has a scope of its own, it can be accessed wherever its parent can. You can't really think of them like a let or const.
Since in Javascript, arguments are passed by value, in this:
let b = "foo"
module.exports.b = b
After this code is executed, module.exports.b has nothing to do with the variable b. It's not a let, or a const it's just a property of module.exports. You could change the value of b and it would have no effect on module.exports.b.
When you're doing module.exports.a = 'a', you're not exporting a variable itself, you're exporting a binding.
Then if when importing you assigng it to const like const {a} = require('./a'), it will be const, if you import it assigning to let {a} = require('./a'), it will be let.
The actual function I provided in this example is not the problem. I just want to know how to save the values of variables after using them as parameters in functions. I was trying to make a very simple function that switches the value of two variables. I know how to do this without a function but wanted to generalize the solution with a function.
I tried writing the following code,
let a = 3;
let b = 4;
const switchItems = (c,d) => {
var temp = c;
c = d;
d = temp;
}
switchItems(a,b);
I expected a to be 4 and b to be 3 but the variables stay the same as they were before the function.
I don't care how to do this with the specific swap function I used, I just want to know how to change a global variable inside a function without referencing it.
You can't.
Javascript's calling semantics mean that the function receives a copy of the reference; if you change it, you're only changing the internal reference. The exception is that you can change values stored in an outer scope (such as the global scope.) But then the values you pass to it are irrelevant.
What you can do, if you like, is to return a swapped copy of the parameter array:
let [b, a] = swap(a, b);
But there's no reason to create such a function, since this is less simple than
let [b, a] = [a, b];
Update
From the question edit:
I just want to know how to change a global variable inside a function without referencing it.
It depends upon what you mean by "change" here. You can assign it a new reference, but only if you have its name, and the parameters are then irrelevant. If you want to change its internals, that's simple:
const foo = {bar: 42}
function baz(x) {
x.bar += 1;
}
baz(foo);
foo //=> {bar: 42}
But it seems you want something more than that, and that's not available.
Consider this code
'use strict';
var factory = () => script => eval(script);
var closure = factory();
closure('var v = 0');
var val = closure('typeof v');
console.log(val);
This is my attempt to achieve it. I want to create a closure and then to allow users to create a new local variable in that closure. Is that even possible?
I somewhere read that "native function 'eval' can even create a new variable in the local execution context.". So, why doesn't it work in my example? My guess is that it is because the function finished executin and number of variables after function ends cannot be changed, but I am not sure.
My example script creates a closure and tries to declare and initialize new variable v in that closure and assign number 0 to it. I expected that the result of typeof v should be number, but it is actually undefined.
So, I have two questions:
Why doesn't it creates the variable v as expected
How to actually achieve that (what would be the working example)?
Well it is locally scoped, but a bit too local
var factory = () =>{
return script =>{
//this is the scope. you cant access any variables outside of this.
eval(script);
};
//you want to eval here, which is impossible
};
You could do very hacky scope stuff to work around this ( store all variables in context):
var factory = (context={}) => script =>{ with(context){ return eval(script); }};
You need to initialize all local variables on creation:
var exec=factory({v:0, b:undefined});// note that you need to set a value explicitly {v,b} wont worm
and then it works as expected:
console.log(
exec("v"),//0
exec("v=2"),//2
exec("v"),//2
typeof v //undefined
);
http://jsbin.com/xibozurizi/edit?console
If you dont want to go that deep, the only thing you could do would be concatenating the strings:
var factory = code => concat => (eval(code),res=eval(concat),code+=concat,res);
// or shorter / more buggy
var factory = code => concat => eval(code+=";"+concat);
var exec=factory("var a=1;");
console.log(
exec("a;"),//1
exec("var b=a+1"),
exec("b"),//2
tyepof a, typeof b //undefined
);
http://jsbin.com/midawahobi/edit?console
The upper code will run the strings multiple times, which is may not wanted. Another approach:
var factory=code=>({
code,
run(c){
return eval(this.code+";"+c);
},
add(c){ this.code+=";"+c}
});
So you can do
var exec=factory("var a='hello'");
exec.run("alert(a)")//alerts hello
exec.add("var b=a+' world'");
console.log(exec.code,exec.run("b"));//hello world, the upper alert isnt run again
http://jsbin.com/lihezuwaxo/edit?console
note that evaling is always a bad idea...
Your call to eval() does create a new variable, and it does so in the local execution context of the call. That context is inside that function, so it doesn't effect the context outside, where you call closure().
Generally eval() is to be strictly avoided. It makes optimization hard (or impossible) so functions that use it will be ignored by the optimizer. There are some rare circumstances in which it's useful, but declaring a new local variable doesn't seem like one of them.
You can do like that
var factory = () => script => eval(script);
var closure = factory();
var v;
closure('v=20')
I have some code like this:
//app.js
var r=require("./second.js");
var a=1;
r.second(a);
console.log(a);
And my second.js looks like this:
//second.js
module.exports.change=function(a){
a=2;
}
Currently console.log(a) only show 1, I did a bit of research saying I can't pass it as a variable because variable is passed as value. I thought about using global.a. However, it doesn't work too because a is stored as a module variable, not a global variable, and I don't want to set it as a global variable.
How do I solve it?
The main answer is that you can't do it exactly as you asked. A variable defined as you have a in app.js is private to the scope of a and only other code within app.js can modify it.
There are a number of other ways you can structure things such that module B can modify something in module A or cause something to be modified in module A.
Global
You can make a global. This is not recommended for a variety of reasons. But you could make it a property of the global object by changing your declaration of a to:
global.a = 1;
Then, in module B, you can directly set:
global.a = 2;
Pass and Return
You can pass a into a function in module B and have that function return a new value that you assign back into a.
const b = require('moduleB');
let a = 1;
a = b.someFunction(a);
console.log(a); // 2
Then, inside of moduleB, you can modify propertys on a directly:
// inside of moduleB
module.exports.someFunction = function(a) {
return ++a;
};
Put value in object and pass reference to object
Rather than storing the value in a simple variable, you can store it as a property of an object an you can then pass a reference to that object. Objects in Javascript are passed by ptr so some other function you pass the object to can modify its properties directly.
const b = require('moduleB');
let a = {someProperty: 1};
b.someFunction(a);
console.log(a.someProperty); // 2
Then, inside of moduleB, you can modify propertys on a directly:
// inside of moduleB
module.exports.someFunction = function(obj) {
obj.someProperty = 2;
};
Initially, there isn't any function that is called second. Your module exposes a function called change. Then when you pass a value to function this value is copied and whatever change you do affects only the copy. So passing the value of a, the change affects only the copy of this value and not the original one. What could you do is to pass an object. This time a copy of the reference to the object is passed. So any change you make is reflected back to the original object (it's like pointers in C and C++).
In terms of code, the changes you should make are the following:
// You could make the method more dynamical passing the value you want a get.
module.exports.change = function(obj, value){
obj.a = value;
}
Last you should call it as:
var r=require("./second.js");
var obj = { a: 1};
r.change(obj,2);
console.log(obj.a);
First way is return in your change function in second.js.
app.js:
var r=require("./second.js");
var a=1;
a = r.change(a);
console.log(a);
second.js:
module.exports.change=function(a){
a=2;
return a;
}
Second way is use object as parameter to pass it by reference:
app.js:
var r=require("./second.js");
var obj={a:1};
r.change(obj);
console.log(obj.a);
second.js:
module.exports.change=function(obj){
obj.a=2;
}
You can use localStorage or sessionStorage for setting and getting your variable.
for node js you can use https://www.npmjs.com/package/localStorage package
//You can set using localStorage.setItem('key','value')
var r = require("./second.js");
var a = 1;
localStorage.setItem('a', 1) /*Window api for setting variable locally
localStorage.setItem('key','value')*/
r.second(a);
console.log(a);
You can get using localStorage.getItem('key')
//second.js
module.exports.change=function(a){
a=localStorage.getItem('a')
}
Let's say we have a function that looks like this:
const fn = () => x;
This function should return the value of x where x is available in the global scope. Initially this is undefined but if we define x:
const x = 42;
Then we can expect fn to return 42.
Now let's say we wanted to render fn as a string. In JavaScript we have toString for this purpose. However let's also say we wanted to eventually execute fn in a new context (i.e. using eval) and so any global references it uses should be internalized either before or during our call to toString.
How can we make x a local variable whose value reflects the global value of x at the time we convert fn to a string? Assume we cannot know x is named x. That said we can assume the variables are contained in the same module.
If you want lock certain variables while converting function to string, you have to pass that variables along the stringified function.
It could be implemented like this (written with types -- typescript notation)
const prepareForEval =
(fn: Function, variablesToLock: { [varName: string]: any }): string => {
const stringifiedVariables = Object.keys(variablesToLock)
.map(varName => `var ${varName}=${JSON.stringify(variablesToLock[varName])};`);
return stringifiedVariables.join("") + fn.toString();
}
Then use it like this
const stringifiedFunction = prepareForEval(someFunction, { x: x, y: y })
// you can even simplify declaration of object, in ES6 you simply write
const stringifiedFunction = prepareForEval(someFunction, { x, y })
// all variables you write into curly braces will be stringified
// and therefor "locked" in time you call prepareForEval()
Any eval will declare stringified variables and funtion in place, where it was executed. This could be problem, you might redeclare some variable to new, unknown value, you must know the name of stringified function to be able to call it or it can produce an error, if you redeclare already declared const variable.
To overcome that issue, you shall implement the stringified function as immediatelly executed anonymous function with its own scope, like
const prepareForEval =
(fn: Function, variablesToLock: { [varName: string]: any }): string => {
const stringifiedVariables = Object.keys(variablesToLock)
.map(varName => `var ${varName}=${JSON.stringify(variablesToLock[varName])};`);
return `
var ${fn.name} = (function() {
${stringifiedVariables.join("")}
return ${fn.toString()};
)();
`;
}
this modification will declare function and variables in separate scope and then it will assign that function to fn.name constant. The variables will not polute the scope, where you eval, it will just declare new fn.name variable and this new variable will be set to deserialized function.
We cannot know x is named x. This is the central piece of this puzzle and is therefore bolded in the original question. While it would be nice if we had a simpler solution, it does seem a proper answer here comes down to implementing some kind of parser or AST traversal.
Why is this necessary? While we can make the assumption that x lives in a module as a global (it's necessarily shared between functions), we cannot assume it has a known name. So then we need some way of extracting x (or all globals really) from our module and then providing it as context when we eventually eval.
N.B.: providing known variables as context is trivial. Several answers here seem to assume that's a difficult problem but in fact it's quite easy to do with eval; simply prepend the context as a string.
So then what's the correct answer here? If we were to use an AST (Acorn may be a viable starting point, for instance) we could examine the module and programmatically extract all the globals therein. This includes x or any other variable that might be shared between our functions; we can even inspect the functions to determine which variables are necessary for their execution.
Again the hope in asking this question originally was to distill a simpler solution or uncover prior art that might be adapted to fit our needs. Ultimately my answer and the answer I'm accepting comes down to the nontrivial task of parsing and extracting globals from a JavaScript module; there doesn't appear to be a simple way. I think this is a fair answer if not a practical one for us to implement today. (We will however address this later as our project grows.)
You can use OR operator || to concatenate current value of x to fn.toString() call
const fn = () => x;
const x = 42;
const _fn = `${fn.toString()} || ${x}`;
console.log(_fn, eval(_fn)());
Global variables can be made local (private) with closures. w3Schools
function myFunction() {
var a = 4;
return a * a;
}
Thanks to guest271314, I now see what you want.
This is his code, just little improved:
const stringifiedFn = `
(function() {
const _a = (${fn.toString()})();
return _a !== undefined ? _a : ${JSON.stringify(fn())};
})();
`;
this code will execute fn in context, where you eval, and if fn in that context returns undefined, it returns the output of fn in context, where it was stringified.
All credit goes to guest271314
do you mean this? only answer can post code, so I use answer
var x = 42
function fn() {
return x
}
(() => {
var x = 56
var localFn = eval('"use strict";(' + fn.toString()+')')
console.log(localFn)
console.log(localFn())
})()
why rename to localFn, if you use var fn=xx in this scope the outer fn never exists!
in nodejs? refer nodejs vm
passing context? you can not save js context unless you maintain your own scope like angularjs
If you're already "going there" by using eval() to execute fn() in the new context, then why not define the function itself using eval()?
eval('const fn = () => ' + x + ';')