8

I am looking for a fancy way to prevent a closure from inheriting surrounding scrope. For example:

let foo = function(t){

  let x = 'y';

  t.bar = function(){

    console.log(x); // => 'y'

  });

};

there are only two ways I know of preventing sharing scope:

(1) Use shadow variables:

let foo = function(t){

  let x = 'y';

  t.bar = function(x){

    console.log(x); // => '?'

  });

};

(2) Put the function body somewhere else:

  let foo = function(t){

      let x = 'y';

      t.bar = createBar();

    };

My question is - does anyone know of a 3rd way to prevent closures from inheriting scope in JS? Something fancy is fine.

The only thing that I think could possibly work is vm.runInThisContext() in Node.js.

Let's use our imaginations for a second, and imagine JS had a private keyword, which meant the variable was private only to that function's scope, like this:

  let foo = function(t){

      private let x = 'y';  // "private" means inaccessible to enclosed functions

      t.bar = function(){

        console.log(x); // => undefined

      });

    };

and IIFE won't work:

let foo = function(t){

    (function() {
    let x = 'y';
    }());

   console.log(x); // undefined (or error will be thrown)
   // I want x defined here

  t.bar = function(){
    // but I do not want x defined here
    console.log(x); 
  }

  return t;
};
Alexander Mills
  • 90,741
  • 139
  • 482
  • 817

2 Answers2

8

You can use block scope

let foo = function(t) {
  {
    // `x` is only defined as `"y"` here
    let x = "y";
  } 
  {
    t.bar = function(x) {
      console.log(x); // `undefined` or `x` passed as parameter
    };
  }
};


const o = {};
foo(o);

o.bar();
guest271314
  • 1
  • 15
  • 104
  • 177
  • 5
    well I will be damned – Alexander Mills Jul 23 '17 at 00:07
  • already upvoted, plus I was awarding $1000 usd only for working IIFE solution :) (although this might use an IIFE under the hood). any other fancy ways you can think of @guest271314? – Alexander Mills Jul 23 '17 at 00:10
  • I had an IIFE solution to your pre-updated use! This is a great solution though. – Kyle Richardson Jul 23 '17 at 00:12
  • There are probably other patterns that can meet requirement. Though why try to determine a process which would necessitate more code than four characters? – guest271314 Jul 23 '17 at 00:13
  • This is the clear winner for me, and I previously advocated IIFE. – Kyle Richardson Jul 23 '17 at 00:14
  • @guest271314 this is for a library not an application so not necessarily looking for most terse solution. – Alexander Mills Jul 23 '17 at 00:14
  • @AlexanderMills Did not ask what the use case is, only attempted to meet requirement. Whether used within a "library" or "application" is immaterial. The original Question specifically asks for one further optional solution _"My question is - does anyone know of a 3rd way"_. Why attempt to include yet another requirement at comments? – guest271314 Jul 23 '17 at 00:18
  • 1
    If you're writing a library, then you're probably doing it wrong if you need this to make it work. This is however a good answer, block scope works great with `let` and `const` which are of course block scoped. – adeneo Jul 23 '17 at 00:18
  • @adeneo well, unfortunately the JS language itself does not do a great job of keeping "private" data *private*...as a library author I sometimes have to jump through hoops to keep private data inaccessible etc. – Alexander Mills Jul 23 '17 at 00:20
  • @guest271314 I was the first or second to upvote your answer - I didn't add another requirement, I just was wondering if you could think of any other way to solve the problem besides block scope. Also, as a request, if you could comment (for everyone's sake) on the availability of block scope in browsers and Node.js, that always helps. – Alexander Mills Jul 23 '17 at 00:21
  • 1
    [Block statements](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block) by themselves are supported in almost all browsers, and have been a part of JS since the beginning, with and without labels etc. However block scoped variables, aka `let` and `const` are only available in [newer browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Browser_compatibility), and this won't work with a regular `var`, which is function scoped. – adeneo Jul 23 '17 at 00:33
  • 1
    Should be noted that if you wanted it to work in older browsers, with `var`, we're back to the extra function to create a new scope, most likely an IIFE again, which would look eerily similar to a block statement but with more characters -> https://jsfiddle.net/adeneo/ydpfaq5w/1/ – adeneo Jul 23 '17 at 00:41
  • @adeneo yes I think that block scope uses IIFE under the hood, it's most likely just syntactic sugar amigo...but TBH the IIFE is fugly and block scope is passable. – Alexander Mills Jul 23 '17 at 00:41
  • @guest271314 why so literal? You know that by the 3rd way, I meant 3rd or 4th or 5th :) – Alexander Mills Jul 23 '17 at 00:42
  • 1
    No they don't use an IIFE internally, then they'd create "function scope", which they don't. Again, blocks have been a part of JS since the beginning, and aren't functions, but they haven't been very useful until block scoped variables became available. – adeneo Jul 23 '17 at 00:43
  • 1
    @AlexanderMills _"why so literal?"_ For clarity as to requirement. – guest271314 Jul 23 '17 at 01:40
  • we might be able to use `with` in JS, see: https://stackoverflow.com/questions/21015806/isolated-execution-context-in-javascript – Alexander Mills Jan 02 '18 at 21:10
  • and: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with – Alexander Mills Jan 02 '18 at 21:10
  • I added an answer that works pretty well, using `new Function()` – Alexander Mills Jan 03 '18 at 17:44
1

This technique works:

Create helper function to run a function in an isolated scope

 const foo = 3;

 it.cb(isolated(h => {
    console.log(foo);  // this will throw "ReferenceError: foo is not defined"
    h.ctn();
 }));

you might also have some luck with the JavaScript with operator

Alexander Mills
  • 90,741
  • 139
  • 482
  • 817