Sandboxing a function to make it safe? [duplicate] - javascript

Suppose I have a variables in the global scope.
Suppose I wish to define a function which I can guarantee will not have access to this variable, is there a way to wrap the function, or call the function, that will ensure this?
In fact, I need any prescribed function to have well defined access to variables, and that access to be defined prior to, and separate from that function definition.
Motivation:
I'm considering the possibility of user submitted functions. I should be able to trust that the function is some variety of "safe" and therefore be happy publishing them on my own site.

Run the code in an iframe hosted on a different Origin. This is the only way to guarantee that untrusted code is sandboxed and prevented from accessing globals or your page's DOM.

Using embedded Web Workers could allow to run safe functions. Something like this allows a user to enter javascript, run it and get the result without having access to your global context.
globalVariable = "I'm global";
document.getElementById('submit').onclick = function() {
createWorker();
}
function createWorker() {
// The text in the textarea is the function you want to run
var fnText = document.getElementById('fnText').value;
// You wrap the function to add a postMessage
// with the function result
var workerTemplate = "\
function userDefined(){" + fnText +
"}\
postMessage(userDefined());\
onmessage = function(e){console.log(e);\
}"
// web workers are normally js files, but using blobs
// you can create them with strings.
var blob = new Blob([workerTemplate], {
type: "text/javascript"
});
var wk = new Worker(window.URL.createObjectURL(blob));
wk.onmessage = function(e) {
// you listen for the return.
console.log('Function result:', e.data);
}
}
<div>Enter a javascript function and click submit</div>
<textarea id="fnText"></textarea>
<button id="submit">
Run the function
</button>
You can try these for example by pasting it in the textarea:
return "I'm a safe function";
You can see that it's safe:
return globalVariable;
You can even have more complex scripts, something like this:
var a = 4, b = 5;
function insideFn(){
// here c is global, but only in the worker context
c = a + b;
}
insideFn();
return c;
See info about webworkers here, especially embedded web workers:
https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Embedded_workers

A little late, but maybe it will help you a bit
function RestrictFunction(params) {
params = ( params == undefined ? {} : params );
var scope = ( params.scope == undefined ? window : params.scope );
var data = ( params.data == undefined ? {} : params.data );
var script = ( params.script == undefined ? '' : params.script );
if (typeof params.script == 'function') {
script = params.script.toString();
script = script.substring(script.indexOf("{") + 1, script.lastIndexOf("}"));
}
// example: override native functions that on the white list
var setTimeout = function(_function,_interval) {
// this is important to prevent the user using `this` in the function and access the DOM
var interval = scope.setTimeout( function() {
RestrictFunction({
scope:scope,
data:data,
script:_function
});
} , _interval );
// Auto clear long user intervals
scope.setTimeout( function() {
scope.clearTimeout(interval);
} , 60*1000 );
return interval;
}
// example: create custom functions
var trace = function(str) {
scope.console.log(str);
}
return (function() {
// remove functions, objects and variables from scope
var queue = [];
var WhiteList = [
"Blob","Boolean","Date","String","Number","Object","Array","Text","Function",
"unescape","escape","encodeURI","encodeURIComponent","parseFloat","parseInt",
"isNaN","isFinite","undefined","NaN",
"JSON","Math","RegExp",
"clearTimeout","setTimeout"
];
var properties = Object.getOwnPropertyNames(scope);
for (var k = 0; k<properties.length; k++ ) {
if (WhiteList.indexOf(properties[k])!=-1) continue;
queue.push("var "+properties[k]+" = undefined;");
}
for (var k in scope) {
if (WhiteList.indexOf(k)!=-1) continue;
queue.push("var "+k+" = undefined;");
}
queue.push("var WhiteList = undefined;");
queue.push("var params = undefined;") ;
queue.push("var scope = undefined;") ;
queue.push("var data = undefined;") ;
queue.push("var k = undefined;");
queue.push("var properties = undefined;");
queue.push("var queue = undefined;");
queue.push("var script = undefined;");
queue.push(script);
try {
return eval( '(function(){'+ queue.join("\n") +'}).apply(data);' );
} catch(err) { }
}).apply(data);
}
Example of use
// dummy to test if we can access the DOM
var dummy = function() {
this.notify = function(msg) {
console.log( msg );
};
}
var result = RestrictFunction({
// Custom data to pass to the user script , Accessible via `this`
data:{
prop1: 'hello world',
prop2: ["hello","world"],
prop3: new dummy()
},
// User custom script as string or function
script:function() {
trace( this );
this.msg = "hello world";
this.prop3.notify(this.msg);
setTimeout( function() {
trace(this);
} , 10 );
trace( data );
trace( params );
trace( scope );
trace( window );
trace( XMLHttpRequest );
trace( eval );
return "done!"; // not required to return value...
},
});
console.log( "result:" , result );

You can't restrict the scope of a Function using the "call" or "apply" methods, but you can use a simple trick using "eval" and scoping to essentially hide any specific global variables from the function to be called.
The reason for this is because the function has access to the "global" variables that are declared at the scope that the function itself what declared. So, by copying the code for the method and injecting it in eval, you can essentially change the global scope of the function you are looking to call. The end result is essentially being able to somewhat sandbox a piece of javascript code.
Here's a full code example:
<html>
<head>
<title>This is the page title.</title>
<script>
function displayTitle()
{
alert(document.title);
}
function callMethod(method)
{
var code = "" +
// replace global "window" in the scope of the eval
"var window = {};" +
// replace global "document" in the scope of the eval
"var document = {}; " +
"(" +
// inject the Function you want to call into the eval
method.toString() +
// call the injected method
")();" +
"";
eval(code);
}
callMethod(displayTitle);
</script>
</head>
<body></body>
</html>
The code that gets eval'd looks like this:
var window = {};
var document = {};
(function displayTitle()
{
alert(document.title);
})();

You can use WebWorkers to isolate your code:
Create a completely separate and parallel execution environment (i.e. a separate thread or process or equivalent construct), and run the rest of these steps asynchronously in that context.
Here is a simple example:
someGlobal = 5;
//As a worker normally take another JavaScript file to execute we convert the function in an URL: http://stackoverflow.com/a/16799132/2576706
function getScriptPath(foo) {
return window.URL.createObjectURL(new Blob([foo], {
type: 'text/javascript'
}));
}
function protectCode(code) {
var worker = new Worker(getScriptPath(code));
}
protectCode('console.log(someGlobal)'); // prints 10
protectCode('console.log(this.someGlobal)');
protectCode('console.log(eval("someGlobal"))');
protectCode('console.log(window.someGlobal)');
This code will return:
Uncaught ReferenceError: someGlobal is not defined
undefined
Uncaught ReferenceError: someGlobal is not defined and
Uncaught ReferenceError: window is not defined
so you code is now safe.

EDIT: This answer does not hide the window.something variables. But it has a clean way to run user-defined code. I am trying to find a way to mask the window variables
You can use the javascript function Function.prototype.bind() to bind the user submitted function to a custom scope variable of your choosing, in this custom scope you can choose which variables to share with the user defined function, and which to hide. For the user defined functions, the code will be able to access the variables you shared using this.variableName. Here is an example to elaborate on the idea:
// A couple of global variable that we will use to test the idea
var sharedGlobal = "I am shared";
var notSharedGlobal = "But I will not be shared";
function submit() {
// Another two function scoped variables that we will also use to test
var sharedFuncScope = "I am in function scope and shared";
var notSharedFuncScope = "I am in function scope but I am not shared";
// The custom scope object, in here you can choose which variables to share with the custom function
var funcScope = {
sharedGlobal: sharedGlobal,
sharedFuncScope: sharedFuncScope
};
// Read the custom function body
var customFnText = document.getElementById("customfn").value;
// create a new function object using the Function constructor, and bind it to our custom-made scope object
var func = new Function(customFnText).bind(funcScope);
// execute the function, and print the output to the page.
document.getElementById("output").innerHTML = JSON.stringify(func());
}
// sample test function body, this will test which of the shared variables does the custom function has access to.
/*
return {
sharedGlobal : this.sharedGlobal || null,
sharedFuncScope : this.sharedFuncScope || null,
notSharedGlobal : this.notSharedGlobal || null,
notSharedFuncScope : this.notSharedFuncScope || null
};
*/
<script type="text/javascript" src="app.js"></script>
<h1>Add your custom body here</h1>
<textarea id="customfn"></textarea>
<br>
<button onclick="submit()">Submit</button>
<br>
<div id="output"></div>
The example does the following:
Accept a function body from the user
When the user clicks submit, the example creates a new function object from the custom body using the Function constructor. In the example we create a custom function with no parameters, but params can be added easily as the first input of the Function constructor
The function is executed, and its output is printed on the screen.
A sample function body is included in comments, that tests which of the variables does the custom function has access to.

Create a local variable with the same name.
If you have a global variable like this:
var globalvar;
In your function:
function noGlobal(); {
var globalvar;
}
If the function refers to globalvar, it will refers to the local one.

In my knowledge, in Javascript, any variable declared outside of a function belongs to the global scope, and is therefore accessible from anywhere in your code.
Each function has its own scope, and any variable declared within that function is only accessible from that function and any nested functions. Local scope in JavaScript is only created by functions, which is also called function scope.
Putting a function inside another function could be one possibility where you could achieve reduced scope ( ie nested scope)

if you are talking about a function that is exposed to you by loading a third party script, you are pretty much out of luck. that's because the scope for the function is defined in the source file it's defined in. sure, you can bind it to something else, but in most cases, that's going to make the function useless if it needs to call other functions or touch any data inside it's own source file - changing it's scope is only really feasible if you can predict what it needs to be able to access, and have access to that yourself - in the case of a third party script that touches data defined inside a closure, object or function that's not in your scope, you can't emulate what might need.
if you have access to the source file then it's pretty simple - look at the source file, see if it attempts to access the variable, and edit the code so it can't.
but assuming you have the function loaded, and it doesn't need to interact with anything other than "window", and for academic reasons you want to do this, here is one way - make a sandbox for it to play in. here's a simple shim wrapper that excludes certain items by name
function suspectCode() {
console.log (window.document.querySelector("#myspan").innerText);
console.log('verbotten_data:',typeof verbotten_data==='string'?verbotten_data:'<BANNED>');
console.log('secret_data:',typeof secret_data==='string'?secret_data:'<BANNED>'); // undefined === we can't
console.log('window.secret_data:',typeof window.secret_data==='string'?window.secret_data:'<BANNED>');
secret_data = 'i changed the secret data !';
console.log('secret_data:',typeof secret_data==='string'?secret_data:'<BANNED>'); // undefined === we can't
console.log('window.secret_data:',typeof window.secret_data==='string'?window.secret_data:'<BANNED>');
}
var verbotten_data = 'a special secret';
window.secret_data = 'special secret.data';
console.log("first call the function directly");
suspectCode() ;
console.log("now run it in a sandbox, which banns 'verbotten_data' and 'secret_data'");
runFunctionInSandbox (suspectCode,[
'verbotten_data','secret_data',
// we can't touch items tied to stack overflows' domain anyway so don't clone it
'sessionStorage','localStorage','caches',
// we don't want the suspect code to beable to run any other suspect code using this method.
'runFunctionInSandbox','runSanitizedFunctionInSandbox','executeSandboxedScript','shim',
]);
function shim(obj,forbidden) {
const valid=Object.keys(obj).filter(function(key){return forbidden.indexOf(key)<0;});
var shimmed = {};
valid.forEach(function(key){
try {
shimmed[key]=obj[key];
} catch(e){
console.log("skipping:",key);
}
});
return shimmed;
}
function fnSrc (fn){
const src = fn.toString();
return src.substring(src.indexOf('{')+1,src.lastIndexOf('}')-1);
}
function fnArgs (fn){
let src = fn.toString();
src = src.substr(src.indexOf('('));
src = src.substr(0,src.indexOf(')')-1);
src = src.substr(1,src.length-2);
return src.split(',');
}
function runSanitizedFunctionInSandbox(fn,forbidden) {
const playground = shim(window,forbidden);
playground.window = playground;
let sandboxed_code = fn.bind(playground,playground.window);
sandboxed_code();
}
function runFunctionInSandbox(fn,forbidden) {
const src = fnSrc(fn);
const args = fnArgs(fn);
executeSandboxedScript(src,args,forbidden);
}
function executeSandboxedScript(sourceCode,arg_names,forbidden) {
var script = document.createElement("script");
script.onload = script.onerror = function(){ this.remove(); };
script.src = "data:text/plain;base64," + btoa(
[
'runSanitizedFunctionInSandbox(function(',
arg_names,
['window'].concat(forbidden),
'){ ',
sourceCode,
'},'+JSON.stringify(forbidden)+')'
].join('\n')
);
document.body.appendChild(script);
}
<span id="myspan">Page Access IS OK<span>
or a slightly more involved version that allows arguments to be passed to the function
function suspectCode(argument1,argument2) {
console.log (window.document.querySelector("#myspan").innerText);
console.log(argument1,argument2);
console.log('verbotten_data:',typeof verbotten_data==='string'?verbotten_data:'<BANNED>');
console.log('secret_data:',typeof secret_data==='string'?secret_data:'<BANNED>'); // undefined === we can't
console.log('window.secret_data:',typeof window.secret_data==='string'?window.secret_data:'<BANNED>');
secret_data = 'i changed the secret data !';
console.log('secret_data:',typeof secret_data==='string'?secret_data:'<BANNED>'); // undefined === we can't
console.log('window.secret_data:',typeof window.secret_data==='string'?window.secret_data:'<BANNED>');
}
var verbotten_data = 'a special secret';
window.secret_data = 'special secret.data';
console.log("first call the function directly");
suspectCode('hello','world') ;
console.log("now run it in a sandbox, which banns 'verbotten_data' and 'secret_data'");
runFunctionInSandbox (suspectCode,['hello','sandboxed-world'],
[
'verbotten_data','secret_data',
// we can't touch items tied to stack overflows' domain anyway so don't clone it
'sessionStorage','localStorage','caches',
// we don't want the suspect code to beable to run any other suspect code using this method.
'runFunctionInSandbox','runSanitizedFunctionInSandbox','executeSandboxedScript','shim',
]);
function shim(obj,forbidden) {
const valid=Object.keys(obj).filter(function(key){return forbidden.indexOf(key)<0;});
var shimmed = {};
valid.forEach(function(key){
try {
shimmed[key]=obj[key];
} catch(e){
console.log("skipping:",key);
}
});
return shimmed;
}
function fnSrc (fn){
const src = fn.toString();
return src.substring(src.indexOf('{')+1,src.lastIndexOf('}')-1);
}
function fnArgs (fn){
let src = fn.toString();
src = src.substr(src.indexOf('('));
src = src.substr(0,src.indexOf(')'));
src = src.substr(1,src.length);
return src.split(',');
}
function runSanitizedFunctionInSandbox(fn,args,forbidden) {
const playground = shim(window,forbidden);
playground.window = playground;
let sandboxed_code = fn.bind(playground,playground.window);
sandboxed_code.apply(this,new Array(forbidden.length).concat(args));
}
function runFunctionInSandbox(fn,args,forbidden) {
const src = fnSrc(fn);
const arg_names = fnArgs(fn);
executeSandboxedScript(src,args,arg_names,forbidden);
}
function executeSandboxedScript(sourceCode,args,arg_names,forbidden) {
var script = document.createElement("script");
script.onload = script.onerror = function(){ this.remove(); };
let id = "exec"+Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString();
window.execArgs=window.execArgs||{};
window.execArgs[id]=args;
let script_src = [
'runSanitizedFunctionInSandbox(function(',
['window'].concat(forbidden),
(arg_names.length===0?'':','+arg_names.join(","))+'){',
sourceCode,
'},',
'window.execArgs["'+id+'"],',
JSON.stringify(forbidden)+');',
'delete window.execArgs["'+id+'"];'
].join('\n');
let script_b64 = btoa(script_src);
script.src = "data:text/plain;base64," +script_b64;
document.body.appendChild(script);
}
<span id="myspan">hello computer...</span>

I verified #josh3736's answer but he didn't leave an example
Here's one to verify it works
parent.html
<h1>parent</h1>
<script>
abc = 'parent';
function foo() {
console.log('parent foo: abc = ', abc);
}
</script>
<iframe></iframe>
<script>
const iframe = document.querySelector('iframe');
iframe.addEventListener('load', function() {
console.log('-calling from parent-');
iframe.contentWindow.foo();
});
iframe.src = 'child.html';
</script>
child.html
<h1>
child
</h1>
<script>
abc = 'child';
function foo() {
console.log('child foo: abc = ', abc);
}
console.log('-calling from child-');
parent.foo();
</script>
When run it prints
-calling from child-
parent foo: abc = parent
-calling from parent-
child foo: abc = child
Both child and parent have a variable abc and a function foo.
When the child calls into the parent's foo that function in the parent sees the parent's global variables and when the parent calls the child's foo that function sees the child's global variables.
This also works for eval.
parent.html
<h1>parent</h1>
<iframe></iframe>
<script>
const iframe = document.querySelector('iframe');
iframe.addEventListener('load', function() {
console.log('-call from parent-');
const fn = iframe.contentWindow.makeFn(`(
function() {
return abc;
}
)`);
console.log('from fn:', fn());
});
iframe.src = 'child.html';
</script>
child.html
<h1>
child
</h1>
<script>
abc = 'child';
function makeFn(s) {
return eval(s);
}
</script>
When run it prints
-call from parent-
from fn: child
showing that it saw the child's abc variable not the parent's
note: if you create iframes programmatically they seem to have to be added to the DOM or else they won't load. So for example
function loadIFrame(src) {
return new Promise((resolve) => {
const iframe = document.createElement('iframe');
iframe.addEventListener('load', resolve);
iframe.src = src;
iframe.style.display = 'none';
document.body.appendChild(iframe); // iframes don't load if not in the document?!?!
});
}
Of course in the child above we saw that the child can reach into the parent so this code is NOT SANDBOXED. You'd probably have to add some stuff to hide the various ways to access the parent if you want make sure the child can't get back but at least as a start you can apparently use this technique to give code a different global scope.
Also note that of course the iframes must be in the same domain as the parent.

Here's another answer. This one's based on how chained scopes work in javascript. It also uses Function(), which produces faster code than eval.
/** This takes a string 'expr', e.g. 'Math.max(x,1)' and returns a function (x,y)=>Math.max(x,1).
* It protects against malicious input strings by making it so that, for the function,
* (1) no names are in scope other than the parameters 'x' and 'y' and a whitelist
* of other names like 'Math' and 'Array'; (2) also 'this' binds to the empty object {}.
* I don't think there's any way for malicious strings to have any effect.
* Warning: if you add something into the global scope after calling make_fn but
* before executing the function you get back, it won't protect that new thing.
*/
function make_fn(expr) {
const whitelist = ['Math', 'Array']; // Warning: not 'Function'
let scope = {};
for (let obj = this; obj; obj = Object.getPrototypeOf(obj)) {
Object.getOwnPropertyNames(obj).forEach(name => scope[name] = undefined);
}
whitelist.forEach(name => scope[name] = this[name]);
const fn = Function("scope","x","y","with (scope) return "+expr).bind({});
return (x,y) => fn(scope,x,y);
}
This is how it behaves: https://jsfiddle.net/rkq5otme/
make_fn("x+y")(3,5) ==> 8
make_fn("Math.max(x,y)")(3,5) ==> 5
make_fn("this")(3,5) ==> {}
make_fn("alert('oops')") ==> TypeError: alert is not a function
make_fn("trace((function(){return this}()))")(3,5) ==> ReferenceError: trace is not defined
Explanation. Consider a simpler version
Function("x", "return Math.max(x, window, this);")
This creates a function with the specified body which has two chained scopes: (1) the function scope which binds x, (2) the global scope. Let's spell out how the symbols Math, x, window and this are all resolved.
x is bound to the property in the function scope
window is bound to the property in the next chained scope, global, i.e. window['window'].
We don't want this! To prevent it, we create our own scope object scope = {window:undefined,...} and write with (scope) return Math.max(x,window,this). The with statement is only allowed in non-strict code. It adds an additional scope, so the chain is now: (0) the specified scope object we created, (1) the function scope which binds x, (2) the global scope. Because name 'window' is found in our scope object, it can never bind to the one in global scope.
Math will suffer the same fate.
We want to whitelist it! So we make our scope object {Math:global.Math, window:undefined, ...}
this is bound upon invocation of the function to the global object, window.
We don't want this! To prevent it, we call .bind({}) on the function, which wraps the function in a wrapper which sets this={}. Alas bind(null) didn't seem to bind.
Note: there's another possible construction of the scope object, using Proxy. It doesn't seem particularly better.
const scope = new Proxy({}, {
has: (obj, key) => !['x','y'].includes(key),
get: (obj, key) => (whitelist.includes(key)) ? this[key] : undefined,
set: (obj, key, value) => {},
deleteProperty: (obj, key) => {},
enumerate: (obj, key) => [],
ownKeys: (obj, key) => [],
defineProperty: (obj, key, desc) => {},
getOwnPropertyDescriptor: (obj, key) => undefined,
});

Related

Override JavaScript (window) Function

I'd like to override a function which is being made by a javascript plugin. I know how to override regular window functions, but this is different. I'm not sure how to name it, but the structure of that function is like:
window.something.function
I have no idea how to override that. I have tried the following:
var originalFunction = window.something.function;
window.something.function = function(parameter) {
alert('called');
return originalFunction(parameter);
}
But it's not working.
Does someone know a solution?
Edit:
As I have been told my question is unclear, I have edited it again using the actual names of the plug-in.
The plugin is meant to be used as:
var myColor = new jscolor(target, options)
When this is being used, there is a function "inside" the object "jscolor" which is being called when setting the value of target element. I want to override that function to add an extra functionality without changing the original js file.
Code:
if (!window.jscolor) { window.jscolor = (function () {
var jsc = {
.....
jscolor : function (targetElement, options) {
....
//Function I want to change:
this.exportColor = function (flags) {
if (!(flags & jsc.leaveValue) && this.valueElement) {
var value = this.toString();
if (this.uppercase) { value = value.toUpperCase(); }
if (this.hash) { value = '#' + value; }
if (jsc.isElementType(this.valueElement, 'input')) {
this.valueElement.value = value;
} else {
this.valueElement.innerHTML = value;
}
}
}
}
};
My attempts so far:
var origJsColor = jscolor.exportColor;
jscolor.exportColor = function(flags) {
console.log('called');
return origJsColor(flags);
}
and the window attempt above.
The jscolor code you've shown creates an object with its own copy of exportColor (one is created for each object). So to replace it, you have to replace it on each instance as the instance is created.
You can do that as a one-off in much the way you showed, just working with the instance rather than the plugin function, and using Function#call to call it with the right this:
// Get the instance
var c = new jscolor(target, options)
// Update it
var origExportColor = c.exportColor;
c.exportColor = function(flags) {
console.log('called');
return origExportColor.call(c, flags); // Note the changes on this line
};
Or instead of
return origExportColor.call(c, flags);
you might use
return origExportColor.apply(c, arguments);
...if there's any chance of the function being called with anything other than exactly one argument. (arguments is a magic pseudo-array containing the arguments used to call the function.)
If you want to do that for all instance you might create, you can put a facade in front of jscolor to do that to each instance:
var realJscolor = jscolor;
jscolor = function() {
// Call the real function, passing along all the arguments we
// get automatically (`arguments` is a magic pseudo-array)
var retVal = realJscolor.apply(this, arguments);
// If it returned a non-`null` object, we want to use that instead
// of `this`; if not, we keep using `this`
if (!retVal || typeof retVal !== "object") {
retVal = this;
}
// Slip in our version of exportColor
var origExportColor = retVal.exportColor;
retVal.exportColor = function(flags) {
console.log('called');
// (Maybe use `apply` here instead)
return origExportColor.call(retVal, flags);
};
// Return the result, in case the real function overrode `this`
return retVal;
};
jscolor.prototype = realJscolor.prototype;
Then just use jscolor normally:
var c = new jscolor(target, options);
The reason for the retVal thing is that although normally a new expression's result is a refernece to the new object created by new, a constructor function can return a non-null object reference and, if it does, the new expression's result is that object reference instead. That's why we check the return value of realJscolor.
Of course, that means that all uses of jscolor on the page that use the global will now use your updated function instead. If you don't want that, just use your own name and don't override jscolor:
var myColor = function() {
var retVal = jscolor.apply(this, arguments);
// ...and so on...
return retVal;
};
myColor.prototype = jscolor.prototype;
Usage:
var c = new myColor(target, options);
Function
function a() {alert(this)} // will print `window` obejct
is defined in the window scope. That is, it is a method of the window. Your more difficult situation comes from the fact that this is different from window if you define function as a method in another object.
var a = {method: function() {alert(this)}}
you call a.method() but see that the same window again. You need to bind your function to the parent object to make it compete method.

Creating JS library with dynamic function calling

I'm creating a JS 'library' file, But I want to encapsulate it in it's entirety within an object, to avoid contaminating namespace of the pages that include the file
The twist to this is within a function inside the library I need to call others functions within library by name, eg using window[]
The code below is just a sample there would actually be several hundred functions that could be called by name. It's this that's caused by trouble as I can't get window[] to reference the function, what's the right way to go about this?
I have tried this, in host page:
<script src= "mylib.js"></script>
var oMyLib = new cMyLib(); //there will only ever be one 'instance' of this
In mylib.js everything is contained in one function:
function cMyLib() {
this.doStuff = function () {
someFunc(this); //call another function in the lib
}
// I tried it with prototypes also
// cMyLib.prototype.doStuff = function () {
// someFunc();
// }
function someFunc(that) {
var s='anotherFunc1'
var f = window[s]; //undefined!
f();
s='anotherFunc2'
f=window[s];
f();
}
function anotherFunc1() {}
function anotherFunc2() {}
}
The functions that you want to reference by name (or actually by number, according to your comments) should be part of that object, and not accessed via window, e.g.:
function cMyLib() {
// allow call without new
if (! (this instanceof cMyLib)) {
return new cMyLib();
}
// enforce singleton
if (this.constructor.singleton) {
return this.constructor.singleton;
} else {
Object.defineProperty(this.constructor, 'singleton', {
value: this
});
}
// instruction array (no need to expose via `this`)
var insn = [];
insn[0x4c] = function lda_immediate() { ... }
// instruction execution
this.step = function() {
var opcode = memory[pc++];
if (opcode in insn) {
// `.call` ensures `this` is set inside the instruction fn.
insn[opcode].call(this);
} else {
hcf();
}
}
}
Note the extra stuff at the top - convenience code to ensure that only one cMyLib can exist.
As long as a function is in parent scope you can just reference it directly, i.e.
function someFunc(that) {
anotherFunc1();
};
will simply work.
Another thing is that classical way to do this is to wrap everything in a self-calling anonymous function, i.e.
(function() {
function anotherFunc1() {};
function anotherFunc2() {};
this.cMyLib = function() { ... };
})();
But your approach is fine as well.
If you wish to call your functions by dynamic name, then you can store them in a top level object:
(function() {
var my_functions = {
anotherFunc1: function() {},
anotherFunc2: function() {}
};
this.cMyLib = function() {
var name = 'anotherFunc1';
my_functions[name]();
};
})();
It's like creating an isolated "global" scope.
Side note: Do not use eval. It is very unsafe and slow. It will backfire at you at some point.

Making JavaScript private methods accessible to its public methods

I understand there are couple of patterns to make JavaScript 'class-like'.
I would like to take the 'extending by prototype' way... simply because it looks more neat. I am not worried about performance much here...
In the below example I have a class (basically function) MetricsChart. I have couple of public methods and one private method (basically a reusable method).
Here from the public method (drawOrAdd) I can't access the private method (_convertArrayToTable), how can I do that?
function MetricsChart(containerId, chartType) {
this._container = document.getElementById(containerId);
this._chartType = chartType;
this._isChartDrawn = false;
this._chartData = null;
var _convertArrayToTable = function (data) {
return google.visualization.arrayToDataTable(data);
}
}
MetricsChart.prototype.drawOrAdd = function(data)
{
if (!this.isChartDrawn()) {
var chart = new google.visualization.LineChart(this._container);
if (chart) {
var table = _convertArrayToTable(data);
console.log(table);
this._isChartDrawn = true;
}
}
}
MetricsChart.prototype.isChartDrawn = function () {
return this._isChartDrawn;
}
MetricsChart.prototype.getChartData = function () {
}
One way I accidentally found was to enclose the public methods inside the MetricsChart class itself...
It works for me :): I can access the public methods outside and the public method can access the private method (serves the purpose).
Below code... Is this right? Am I doing anything wrong?
function MetricsChart(containerId, chartType) {
this._container = document.getElementById(containerId);
this._chartType = chartType;
this._isChartDrawn = false;
this._chartData = null;
var _convertArrayToTable = function (data) {
return google.visualization.arrayToDataTable(data);
}
MetricsChart.prototype.drawOrAdd = function (data) {
if (!this.isChartDrawn()) {
var chart = new google.visualization.LineChart(this._container);
if (chart) {
var table = _convertArrayToTable(data);
console.log(table);
this._isChartDrawn = true;
}
}
}
MetricsChart.prototype.isChartDrawn = function () {
return this._isChartDrawn;
}
MetricsChart.prototype.getChartData = function () {
}
}
So, here a couple of things, in order to understand what you have done precisely.
First of all:
function foo() {
var i = 0;
function bar() {
return true;
}
}
What's happening here: every time the function foo is called, it creates in its scope a new variable i, and a new function bar. The function bar and the variable i are in its scope, it means they're local: there is no way, with this code, to access to either i or bar outside the function foo. Also because, once the function foo is terminated, both i and bar are disposed.
So, this is why you cannot access from your "public" method to the "private" one, and I hope it's more clear now. The only way for a function to access to a function or variable is that there is a reference shared in the same scope. So, this is what you have done in your last example: you define your "public" methods in the same scope where you define your "private" method. In this way they can access each other. However, the way you have done, has a big downside. As I said previously, the function bar is created every time the function foo is called. In a "class" example, it means:
function MyClass() {
function myprivate() {}
MyClass.prototype.mypublic = function () { return myprivate() }
}
It means that every time you're creating an instance of MyClass, you're creating two new functions, and you're rewrite all the time the prototype of your "class". This is far from be a good approach. In fact, if you have something like:
var a = new MyClass();
var _mypublic = a.mypublic;
var b = new MyClass();
console.log(_mypublic === b.mypublic) // false
console.log(_mypublic === a.mypublic) // false too!
So, you guess right but you executed wrong. What you need here is a the "module pattern": nowadays you can use CommonJS module in nodejs or AMD in browser and so on, but the basic idea is defined a "scope" and exports from this scope only what you want. In your case, you could have:
// this is your "module"
;(function(exports) {
// your local (private) function
var _convertArrayToTable = function (data) {
return google.visualization.arrayToDataTable(data);
}
function MetricsChart(containerId, chartType) {
this._container = document.getElementById(containerId);
this._chartType = chartType;
this._isChartDrawn = false;
this._chartData = null;
}
MetricsChart.prototype.drawOrAdd = function(data) {
if (!this.isChartDrawn()) {
var chart = new google.visualization.LineChart(this._container);
if (chart) {
var table = _convertArrayToTable(data);
console.log(table);
this._isChartDrawn = true;
}
}
}
// you decided to exports to the main scope what you want
exports.MetricsChart = MetricsChart;
}(this)); // here `this` is the global object
And that's it. You have created a closure, using the "module pattern", and from the "public" method you can access to the "private" function, because they're defined in the same scope. But because you do not do that in the "class" constructor, you don't redefine them every time you instantiate a new object. Therefore, the previous example written in this way, will give the right result:
var a = new MyClass();
var _mypublic = a.mypublic;
var b = new MyClass();
console.log(_mypublic === b.mypublic) // true
console.log(_mypublic === a.mypublic) // true
What you've done isn't necessarily "wrong"...it just looks weird. Also, you won't be able to access "MetricsChart.prototype.*" until after you've created an instance of "MetricsChart". Depending on how you are using this object, it may not matter.
That being said, another way is to keep your original structure, but move the following outside of the constructor:
var _convertArrayToTable = function (data) {
return google.visualization.arrayToDataTable(data);
}
It would still be private to your module which should be good enough (you are using modules right?).
What you have done works perfectly.
You can't inherit private methods in any OOP language in terms of overriding them or accessing them directly. They are private. So it makes no sense to have them prototyped for inheritance purposes. You have wrapped them in function scope so they are as "private" as they need to be.
To access the private methods use privilege methods. Check this document: http://javascript.crockford.com/private.html.
About your code check this answer:
Setting javascript prototype function within object class declaration
p.s.
function Test()
{
var p = function(pv)
{
//
}
this.e = function (ap) { p(ap) }
}
var n = new Test();
n.e("e"); // It is legal call
n.p(); // will throw
But if you declare a private function in c-tor it will be executed on first creation of object of this type. When declare a methods in prototype this methods are add before any code execution. In general the browser first check the js file to collect all methods for prototype and than execute any code. So when you declare a prototype methods into c-tor this methods will be available only after first creation of the object of those type. ( Sorry for my English ).
Check this situation:
function Test()
{
alert(this.ST_A);//alert undefined
alert(this.ST_B);//alert 2
Test.prototype.ST_A = 1;
alert( this.ST_A)//alert 1
}
Test.prototype.ST_B = 2;
In first pass the browser will populate Test with ST_B and ST_B will be available anywhere any time. After than in second pass the browser will start to execute the code in this time ST_A will not be visible until the browser execute the Test.prototype.ST_A = 1;

Create a JavaScript function dynamically from a string name

Given a string classname, I want to dynamically create a new JavaScript function named after that string that can be used to instantiate objects.
I've tried using eval() but for some reason the declared function does not appear in the global (window) scope.
eval( "function " + classname + "() {}" );
window[ classname ]; // => undefined
Is there a way I can dynamically create a new function named after a string?
Or, alternatively, give me some way to reference the created function after creating it via eval. Interestingly it appears as a local variable when I debug it in Safari.
Update:
Got it! Of course it's obvious, I just use eval again to create the instance:
var myInstance = eval( "new " + classname );
myInstance.constructor.name; // => classname (yay)
This should work in my case because I only need to create one instance of the class right after it's declared. For the general case though see Pointy's answer.
Yes:
window[classname] = function() { ... };
Now, in honesty, that's not exactly like what you were attempting, but it's pretty close. When you instantiate a function via a function expression like that, and without a name, the function can't refer to itself except via the name in the outer scope (in this case, the global scope).
If that's important, what you could do is this: create the function with some stock "internal" name, and then assign it to the global name:
function secretName() { ... }
window[classname] = secretName;
function registerFunction(functionBody) {
"use strict";
var script = document.createElement("script");
script.innerHTML = "function " + functionBody;
document.body.appendChild(script);
}
registerFunction("fooBar(x, y) { return x + y; }");
fooBar(1, 2); // will give you 3
Although this is essentially the same as eval() but it will register the function in the domain of the current page. You can later remove this script element, or reuse it for other functions.
Try this:
var classname = "myFunction";
window[ classname ] = function () {};
alert( window[ classname ] ); // => undefined
In case you don't want to create new functions based on some string, but based on another similar function: (this might not be a good example but hope you can get the idea)
function createListOfFunctions(functionNameList) {
resultFunctions = {};
// Loop all names to create a list of functions with those names
$.each(functionNameList, function(index, functionName) {
resultFunctions[functionName] = _createFunction(functionName);
});
return resultFunctions;
}
function _createFunction(name) {
return function(anotherNameToCompare) {
// customize this funciton whatever you like
return name == anotherNameToCompare;
};
}
// USAGE:
functionNameList = ['stack', 'overflow'];
result = createListOfFunctions(functionNameList); // result = { stack: function(name) {...}, overflow: function(name) {...} }
result.stack('stack'); // true
result.stack('not stack'); // false
result.overflow('overflow'); // true

Create a new unique global variable each time a function is run in javascript [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Javascript dynamic variable name
A very basic question. I want to create a new javascript global variable each time a function is called. The variable should contain the id of the element so that I can easily access it later.
id = 2347
//this function would be called multiple times, hopefully generating a new global each time
function (id)
{
var + id = something
// I want a variable that would be named var2347 that equals something, but the above line doesn't get it.
}
In a later function, I want to access the variable like so:
function two (id)
{
alert(var + id);
}
I'm sure I'm going to have a "doh!" moment when someone is kind enough to answer this.
How about...
var store = (function() {
var map = {};
return {
set: function ( name, value ) {
map[ name ] = value;
},
get: function ( name ) {
return map[ name ];
}
};
})();
Usage:
store.set( 123, 'some value' );
and then...
store.get( 123 ) // 'some value'
store.get( 456 ) // undefined
Live demo: http://jsfiddle.net/jZfft/
Programmers are highly advised to not declare global variables, since the browsers already ship with several hundreds of names in the global namespace. Using the global namespace for your own variables can lead to name-collisions which can then break the program or some of the browser's functionality. Creating new namespaces is free, so don't be shy to do it...
Global variables are properties of the window object, so window.lol and window['lol'] define a global variable lol which can be accessed in any of these ways.
The second, window['lol'], can also be used with variable names, like this:
var lol = 123;
var name = 'lol';
var content = window[name]; // window['lol'] == 123
content will now contain 123.
Pretty much anything can be put between square brackets [], so you can also do this:
var id = 123;
window['prefix' + id] = 'text';
var content = window['prefix' + id]; // prefix123 == text
Or, in your case:
var id = 2347;
function one(id) {
window['var' + id] = something;
}
function two(id) {
alert(window['var' + id]);
}
You can save your values to the global hash:
var g = {};
function (id)
{
g[id] = something;
}
function two (id)
{
alert(g[id]);
}
I would argue that you don't really want to be making lots of global variables. Rather, you can just make one global object or array and attach all your other variables to that. In this case, you probably want an object:
var myIds = {};
function makeSomething(id) {
// create something that goes with this id
myIds[id] = something;
}
Then, to fetch that information at some time later, you can retrieve it with this:
var something = myIds[id];
The reason for this suggestion is many-fold. First off, you want to minimize the number of global variables because every global is a chance for a naming collision with some other script you might be using. Second off, when keeping track of a bunch of related data, it's a better programming practice to keep it in one specific data structure rather than just throw it all in the giant global bin with all other data.
It's even possible to create an object that manages all this for you:
function idFactory() {
this.ids = {};
}
idFactory.prototype = {
makeSomething: function(id) {
// create something that goes with this id
this.ids[id] = something;
},
retrieveSomething: function(id) {
return(this.ids[id]);
},
clear: function() {
this.ids = {};
}
};
// then you would use it like this:
var myIds = new idFactory();
myIds.makeSomething(2347);
var value = myIds.retrieveSomething(2347);

Categories

Resources