5

Ok, difficult to understand from the title only. Here is an example. I want a function to refer to a variable that is "injected" automagically, ie:

function abc() {
   console.log(myVariable);
}

I have tried with:

with({myVariable: "value"}) { abc() } 

but this doesn't work unless abc is declared within the with block, ie:

with({myVariable: "value"}) { 

    function abc() {
       console.log(myVariable);
    }

    abc();  // This will work

}

So the last piece will work, but is it possible to fake the with statement, or do I have to force the developers to declare their function calls in a with statement?

Basically the call I want to do is:

doSomething({myVariable: "value"}, function() {
    console.log(myVariable);
});

Ofcourse, I am aware I could pass this is a one parameter object, but that is not what I am trying to do:

doSomething({myVariable: "value"}, function(M) {
    console.log(M.myVariable);
});

Further more, I am trying to avoid using eval:

with({myVariable: "value"}) { 

    eval(abc.toString())(); // Will also work

}

Is this not supported at at all beyond eval in Javascript?

mjs
  • 21,431
  • 31
  • 118
  • 200
  • 2
    Why are you trying to avoid using an object? Seems like the best solution. `with` and `eval` are slow, and `with` will throw error in ES5. – elclanrs Jul 17 '13 at 00:08
  • 1
    JavaScript has lexical scope, so that is not possible. – Felix Kling Jul 17 '13 at 00:09
  • 1
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures is almost related – bbill Jul 17 '13 at 00:11
  • I am trying to develope something more developer friendly, ideally without the Object would be better. Yes, what is wrong with the with statement!? I noticed strange errors in Firefox, for instance when I added comments before an eval statement using with. Anyways, is there a better way to use a with ? Sometimes they are neccessary, like in a template engine... with ( model ) { eval("myVariable") } – mjs Jul 17 '13 at 00:11
  • 1
    This isn’t supported at all, even *with* `eval`. This is not a thing you should do. What are you trying to accomplish? – Ry- Jul 17 '13 at 00:13
  • Check here [Why is `with` deprecated](http://www.2ality.com/2011/06/with-statement.html) – elclanrs Jul 17 '13 at 00:13
  • 1
    If it’s for a template engine, force people to reference the object, e.g. `data.users` instead of `users`. – Ry- Jul 17 '13 at 00:14
  • 2
    The `eval` solution has the problem that your are destroying the function the developer passed and if the function has free variables (i.e. it is a closure) it won't work as expected. Personally I find an API more "developer friendly" if I can easily follow/understand how it works. Doing some "magic" (if there is any) in the back is not a good idea IMO. – Felix Kling Jul 17 '13 at 00:14
  • 3
    FWIW I would recommend telling developers to write `this.variable` instead of just `variable` in the function, then you can `.call` their callback and supply context. Of course this is only doable if you don't want to use `this` for something else. – Jon Jul 17 '13 at 00:17
  • @minitech Yes, that will work with the eval.. try it ... but Felix is right ... it might destroy closure contexts ... – mjs Jul 17 '13 at 00:23
  • @Hamidam: That’s why I’m saying it *won’t* work. That’s completely broken. – Ry- Jul 17 '13 at 00:25
  • possible duplicate of [Is it possible to achieve dynamic scoping in JavaScript without resorting to eval?](http://stackoverflow.com/questions/10060857/is-it-possible-to-achieve-dynamic-scoping-in-javascript-without-resorting-to-eva) – Aadit M Shah Jul 17 '13 at 02:48

8 Answers8

4

JavaScript does not provide any straightforward way to achieve the syntax you're looking for. The only way to inject a variable into a Lexical Environment is by using eval (or the very similar Function constructor). Some of the answers to this question suggest this. Some other answers suggest using global variables as a workaround. Each of those solutions have their own caveats, though.

Other than that, your only option is to use a different syntax. The closest you can get to your original syntax is passing a parameter from doSomething to the callback, as Aadit M Shah suggested. Yes, I am aware you said you don't want to do that, but it's either that or an ugly hack...


Original answer (written when I didn't fully understand the question)

Maybe what you're looking for is a closure? Something like this:

var myVariable = "value";
function doSomething() {
    console.log(myVariable);
};
doSomething(); // logs "value"

Or maybe this?

function createClosure(myVariable) {
    return function() {
        console.log(myVariable);
    };
}
var closure = createClosure("value");
closure(); // logs "value"

Or even:

var closure = function(myVariable) {
    return function() {
        console.log(myVariable);
    };
}("value");
closure(); // logs "value"
Community
  • 1
  • 1
bfavaretto
  • 71,580
  • 16
  • 111
  • 150
2

Try:

function doSomething(vars, fun) {
    for (var key in vars) { // set the variables in vars
        window[key] = vars[key];
    }
    fun.call(); // call function
    for (var key in vars) { // remove the variables again. this will allow only the function to use it
        delete window[key];
    }
}

Set global variables that can then be received inside of fun

The JSFiddle: http://jsfiddle.net/shawn31313/MbAMQ/

Shawn31313
  • 5,978
  • 4
  • 38
  • 80
  • 3
    Didn't even think of that, but of course this can lead to all sorts of other problems. – Felix Kling Jul 17 '13 at 00:12
  • Global namespace pollution...just say no. – Rob Raisch Jul 17 '13 at 00:13
  • @RobRaisch Seems like the only way to go. My code also removes the variable again though. This is probably going to be the only solution. – Shawn31313 Jul 17 '13 at 00:14
  • @Shawn31313: What if the variable existed before calling `doSomething`? You can see where this is going. It might be possible to brute force it, but the smell is horrible. – Jon Jul 17 '13 at 00:15
  • Yes, this is one way, I have to give it you for finding a solution which I asked for, but I have to agree with Rob and Felix, I am sure this can lead to a host of other issues, for instance concurrency... If one could only create another scope besides the global scope, an own scope to call a method with... with sounds like it should be solution but no ... there should be an option to pollution the window object... – mjs Jul 17 '13 at 00:15
  • @Hamidam: JavaScript is single-threaded. (Not to say there aren’t a whole host of other issues, for instance other functions called by the function.) – Ry- Jul 17 '13 at 00:16
  • Well what if I just make the code conserve the value of an already set variable. – Shawn31313 Jul 17 '13 at 00:17
  • @minitech not really, what if fun is : function fun() { setTimeout(function() { alert(myVariable) }, 3000); }) ... I would call that threaded ... or for instance you have asynchroenous javascript loading, ajax calls and so forth ... Maybe not threaded in classical sence, but I wouldn't agree that it is fully sequential either .. – mjs Jul 17 '13 at 00:19
  • @Hamidam: It’s not sequential, but it’s not concurrent. All events call back to a single main thread. – Ry- Jul 17 '13 at 00:20
  • @minitech That would be called threads. In a single core processor computer, you can still create threads, but they are handled by one main process/thread through time slicing/sharing. Even Java threads work like this: "... in systems that only have a single execution core, and thus >>>>only have one thread actually executing at any given moment<<<<. Processing time for a single core is shared among processes and threads through an OS feature called time slicing." http://docs.oracle.com/javase/tutorial/essential/concurrency/procthread.html – mjs Jul 17 '13 at 00:28
  • 2
    @Hamidam: That’s different; you run into more concurrency issues there, like floating-point numbers being updated and read at the same time. In JavaScript, a “thread” (which is not an OS thread and again, not even a thread at all) doesn’t stop until it makes another non-blocking call and exits. – Ry- Jul 17 '13 at 00:40
2

Warning: disgusting code ahead

function callWithContext(func, context, args) {
    var oldProperties = {};

    for(var n in context) {
        if(context.hasOwnProperty(n)) {
            var oldProperty = Object.getOwnPropertyDescriptor(self, n);
            oldProperties[n] = oldProperty;

            (function(n) {
                Object.defineProperty(self, n, {
                    get: function() {
                        if(arguments.callee.caller === func) {
                            return context[n];
                        }

                        if(!oldProperty) {
                            return;
                        }

                        if(oldProperty.get) {
                            return oldProperty.get.apply(this, arguments);
                        }

                        return oldProperty.value;
                    },
                    set: function(value) {
                        if(arguments.callee.caller === func) {
                            context[n] = value;
                        }

                        if(!oldProperty) {
                            return;
                        }

                        if(oldProperty.set) {
                            return oldProperty.get.apply(this, arguments);
                        } else if(!oldProperty.writable) {
                            var fakeObject = {};
                            Object.defineProperty(fakeObject, n, {value: null, writable: false});
                            fakeObject[n] = value; // Kind of stupid, but…
                            return;
                        }

                        oldProperty.value = value;
                    }
                });
            })(n);
        }
    }

    func.apply(this, args);

    for(var n in context) {
        if(context.hasOwnProperty(n)) {
            if(oldProperties[n]) {
                Object.defineProperty(self, n, oldProperties[n]);
            } else {
                delete self[n];
            }
        }
    }
}

This is vomitously horrendous, by the way; don’t use it. But ew, it actually works.

Ry-
  • 218,210
  • 55
  • 464
  • 476
  • @Shawn31313: To show how it’s possible to beat calling functions (well, as long as the functions aren’t both defined inside the called function and trying to use something in the `context` object). – Ry- Jul 17 '13 at 00:38
  • @minitech Nicely done, now I just have to figure out what this code is actually doing ... that might take some time haha :) – mjs Jul 17 '13 at 00:42
  • 1
    @Hamidam: Okay, okay. I will do this. Reluctantly. :) It copies over all properties from `context`, checking for previously-existing properties, and checks the caller of the property (which is put on `self`, which is the global object [`window` or `global`, usually, depending on the engine]) and returns the appropriate thing. Then it calls the function. Finally, it restores the original properties. Calls to this can safely be nested, but the one caveat is that functions declared inside the target function can’t access things from `context`. – Ry- Jul 17 '13 at 00:55
  • 2
    If I understand the code, it's the most complicated way to set global variables I've ever seen! – bfavaretto Jul 17 '13 at 02:00
  • @bfavaretto: It restores global variables, checks a caller, and is compatible with other properties (and therefore nestable). I wouldn’t use a single solution out of all of the answers on this question, though. – Ry- Jul 17 '13 at 03:33
  • @minitech You haven't seen my answer yet. =) – Aadit M Shah Jul 17 '13 at 04:53
  • @AaditMShah: I have, and my point remains. I also don’t think your answer adds much beyond the `eval` the question wanted to avoid. – Ry- Jul 17 '13 at 05:11
2

I asked a similar question a long time ago: Is it possible to achieve dynamic scoping in JavaScript without resorting to eval?

The short answer is no, you can't achieve dynamic scoping without resorting to eval. The long answer is, you don't need to.

JavaScript doesn't support dynamic scoping, but that's not an issue because you can make your free variables parameters of the function that they belong to.

In my humble opinion this is the best solution:

function doSomething(context, callback) {
    callback(context);
}

doSomething({myVariable: "value"}, function(M) {
    console.log(M.myVariable);
});

However since you don't want to write a formal parameter, the next best thing is to use this instead:

function doSomething(context, callback) {
    callback.call(context);
}

doSomething({myVariable: "value"}, function() {
    console.log(this.myVariable);
});

Another option would be to manipulate the formal parameter list of the program as follows:

function inject(func, properties) {
    var args = [], params = [];

    for (var property in properties) {
        if (properties.hasOwnProperty(property)) {
            args.push(properties[property]);
            params.push(property);
        }
    }

    return Function.apply(null, params.concat("return " + func.toString()))
        .apply(null, args);
}

Now we can use this inject method to inject properties into a function as follows:

function doSomething(context, callback) {
    var func = inject(callback, context);
    func();
}

doSomething({myVariable: "value"}, function() {
    console.log(myVariable);
});

See the demo: http://jsfiddle.net/sDKga/1/

Note: The inject function will create an entirely new function which will not have the same lexical scope as the original function. Hence functions with free variables and partially applied functions will not work as expected. Only use inject with normal functions.

The Function constructor is kind of like eval but it's much safer. Of course I would advise you to simply use a formal parameter or this instead. However the design decision is your choice.

Community
  • 1
  • 1
Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • 1
    Even more bizarre, *two* downvotes. This answer should be at the top. I guess they're downvoting because they're disgusted by how inject works (although I wouldn't call it "vomitously horrendous"). The fact is, it works (as other solutions here work), and its' up to the OP to decide what to use. And I agree with your advice to stick with standard language constructs if possible. – bfavaretto Jul 17 '13 at 03:13
  • 1
    I agree that this is pretty good, it doesn't deserve to be downvoted, however it is using Eval through Function but eval is not in it the code, I I think you didn't violate any terms per sé :) I am upvoting anyway! – mjs Jul 17 '13 at 09:39
  • @bfavaretto I made the `inject` function even less "vomitously horrendous". =) – Aadit M Shah Jul 18 '13 at 02:20
  • @Hamidam I made the `inject` function even more succinct. Nevertheless, it's still bad code. – Aadit M Shah Jul 18 '13 at 02:22
  • 1
    I could swear I had upvoted your answer yesterday, but the site now says I didn't. So here you are, +1! :) – bfavaretto Jul 18 '13 at 02:31
1

i don't see why you can't just pass the info in or define a single global, but i think that would be best.

that said, i am working on a Module maker/runner that allows sloppy/dangerous code to execute without interference to the host environment. that provides the opportunity to re-define variables, which can be passed as an object.

this does use eval (Function() technically) but it can run in "use strict", so it's not too crazy/clever. it doesn't leave behind artifacts. it also won't let globals get hurt.

it's still a work in progress, and i need to iron out a couple minor details before i vouch for security, so don't use it for fort knox or anything, but it's working and stable enough to perform the operation asked for.

tested in ch28, FF22, IE10:

function Module(strCode, blnPreventExtensions, objWhitelist, objExtend) {
   var  __proto__=self.__proto__, pbu=self.__proto__, str=strCode, om=[].map, wasFN=false,
    params = {Object:1}, fnScrubber, natives= [ Object, Array, RegExp, String, Boolean, Date] ,
    nativeSlots = [],
        preamble = "'use strict';" ,
                inherited="__defineGetter__,__defineSetter__,__proto__,valueOf,constructor,__lookupGetter__,__lookupSetter__",
        late = inherited + 
                Object.getOwnPropertyNames(__proto__||{}) + Object.getOwnPropertyNames(window);
    late.split(",").sort().map(function(a) {
        this[a] = 1;
    }, params);

        preamble+=";var "+inherited+";";

        //turn functions into strings, but note that a function was passed
         if(str.call){wasFN=true; str=String(str); delete params.Object; }

           objExtend=objExtend||{};
           var vals=Object.keys(objExtend).map(function(k){ return objExtend[k]; })


        // build a usable clone of Object for all the new OOP methods it provides:
        var fakeOb=Object.bind();
        (Object.getOwnPropertyNames(Object)||Object.keys(Object)).map(function(a){
                 if(Object[a] && Object[a].bind){this[a]=Object[a].bind(Object); } return this;
        },fakeOb)[0];



    //allow "eval" and "arguments" since strict throws if you formalize them and eval is now presumed safe.
        delete params.eval;
        delete params.arguments;
        params.hasOwnProperty=undefined;
        params.toString=undefined;
                params['__proto__']={};
                __proto__=null;


        Object.keys(objWhitelist||{}).map(function ripper(a,b){
                b=this[a];
                if(typeof b!=='object'){ 
                    delete this[a];
                }
         }, params);

        // var ok=Object.keys.bind(Object);

    // prevent new prototype methods from being added to native constructors:
    if (blnPreventExtensions) {
        natives.forEach(function(con, i) {
                       var proto=con.prototype;
                       Object.getOwnPropertyNames(proto).map(function(prop){ 
                              if(proto[prop] && proto[prop].bind ){ this[prop]=proto[prop];} 
                        }, nativeSlots[i] = {});
                         delete con.constructor;
                         delete con.prototype.constructor;  
        }); //end con map()
    } /* end if(blnPreventExtensions) */


    //white-list harmless math utils and prevent hijacking:
    delete params.Math;
    if(blnPreventExtensions){Object.freeze(Math);}

    //prevent literal constructors from getting Function ref (eg: [].constructor.constructor, /./.constructor.constructor, etc...):
    Function.prototype.constructor = null;


    try {
                //generate a private wrapper function to evaluate code:
        var response = Function(
                   Object.keys(objExtend) + (vals.length?",":"") +
           Object.keys(params).filter(/./.test, /^[\w\$]+$/), // localize most globals
           preamble + " return " + str.trim() // cram code into a function body with global-blocking formal parameters
        );


               // call it with a blank this object and only user-supplied arguments:
               if (blnPreventExtensions) {  //( user-land code must run inside here to be secure)
                       response = response.apply({}, vals.concat(fakeOb)).apply({}, [].slice.call(arguments,4) ); 
               }else{
                       response = response.apply({}, vals.concat(fakeOb)); 
               }

    } catch (y) {
        response = y + "!!";
    } /* end try/catch */


    if (blnPreventExtensions) {
        om.call(natives, function(con, i) {
                        var pro=con.prototype;

                        //remove all proto methods for this con to censor any additions made by unsafe code:
                        Object.getOwnPropertyNames(pro).map(function(a){ try{delete pro[a];}catch(y){}});

                        //restore all original props from the backup:
            var bu = nativeSlots[i];
            om.call(Object.keys(bu), function(prop){ con.prototype[prop]=bu[prop]; }, bu);

        }); //end con map()
    } /* end if(blnPreventExtensions) */

       //restore hidden Function constructor property:
       Function.prototype.constructor = Function;
    return response;
} /* end Module() */

/////////////////////////////////////////////////////////////


function doSomething(context, fn){
    console.log(myVariable);
    return myVariable;
}


//use 1:
 alert(    Module(doSomething, true, {console:1}, {myVariable: "value123"} )    );// immed

//use2:
var fn=Module(doSomething, false, {console:1}, {myVariable: "value123"} );// as function
alert(fn);
alert(fn());

again, i think OP would be best off not doing things later than need be, but for the sake of comprehensiveness and inspiration i'm putting this out there in good faith.

dandavis
  • 16,370
  • 5
  • 40
  • 36
0

You need to use call() to construct a context, as in:

var f=function(){
    console.log(this.foo);
};

f.call({foo:'bar'})

will print "bar"

Rob Raisch
  • 17,040
  • 4
  • 48
  • 58
  • 1
    Because I don’t think you understood the question, or at least your answer isn’t complete. Could you not ask “why the downvote” after 16 seconds? People actually take time to type. – Ry- Jul 17 '13 at 00:19
  • 1
    It doesn't directly answer the question, it proposes an alternative solution. I think you should at least mention that. – Felix Kling Jul 17 '13 at 00:19
  • 1
    Not my dv, but you should probably present the answer differently. "This is not doable/should be avoided because ... but you might want to consider this solution which ... ". – Jon Jul 17 '13 at 00:20
  • ^+ this is exactly the same as passing an object. – Ry- Jul 17 '13 at 00:20
  • Yes, this is one way, but not really what I am looking for ... which I realize might be asking too much ... but the window solution above was creative ... if only one could create an own such context! – mjs Jul 17 '13 at 00:32
0

You can avoid using eval() in calling the function, if you are willing to use it in doSomething():

function abc() {
   console.log(myVariable);
}

// Prints "value"
callWith({ myVariable: "value" }, abc);

function callWith(context, func) {
  for(var i in context) eval('var ' + i + ' = context[i];');

  eval('(' + func.toString() + ')')();
}

Have a look at this post.

Russell Zahniser
  • 16,188
  • 39
  • 30
0

Have a look at goog.partial, scroll a little bit up to see the description of what it does:

Here is an implementation of it:

var b = goog.partial(alert, 'Hello world!');
b();//alerts "Hello world!"

In the example it passes the function alert with parameter "Hello world!" but you can pass it your own function with multiple parameters.

This allows you to create a variable that points to a function that is always called with a certain paramater. To use parameters in a function that are not named you can use arguments:

function test(){
  console.log(arguments);//["hello","world"]
}

test("hello","world");
HMR
  • 37,593
  • 24
  • 91
  • 160