14

A function in javascript forms a closure by keeping a (hidden) link to its enclosing scope.

Is it possible to access it programmatically when we have the function (as a variable value) ?

The real goal is theoretical but a demonstration could be to list the properties of the closure.

var x = (function(){
   var y = 5;
   return function() {
       alert(y);
   };
})();

//access y here with x somehow
Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
  • Code sample would better explain your exact problem – Blaster Jun 25 '12 at 16:01
  • The problem is theoretical, not practical : given a function, can you enumerate the properties of its closure ? – Denys Séguret Jun 25 '12 at 16:02
  • 1
    @Blaster: Try it. It won't work. `y` isn't a property on `x`. – Matt Jun 25 '12 at 16:05
  • @Matt: It works for me alerts 5 or I am missing the point here – Blaster Jun 25 '12 at 16:05
  • 1
    @Blaster that's because that's what the "x" function does. Your example would have also logged 5 on the console. – Pointy Jun 25 '12 at 16:06
  • 1
    @Blaster: That's the `alert(y)` that's showing you `5`. The `console.log()` shows `TypeError: Cannot read property 'y' of undefined` – Matt Jun 25 '12 at 16:06
  • In fact, [that seems possible](http://dmitrysoshnikov.com/ecmascript/chapter-6-closures/#ecmascript-closures-implementation). I'll do tests. EDIT : [no more possible](http://whereswalden.com/2010/05/07/spidermonkey-change-du-jour-the-special-__parent__-property-has-been-removed/) – Denys Séguret Jun 25 '12 at 16:17
  • I think the best would be to accept the answer of the first one to point the line in the official documentation of ecmascript where it's specified that the call object has to be kept hidden. – Denys Séguret Jun 25 '12 at 16:26

4 Answers4

5

If you're in a front-end environment, and if you can execute your own Javascript in a prior script tag, an option is to attach a MutationObserver, wait for the script tag you want to spy on to be inserted into the document, and right when it's inserted, change its code so that the functionality you want to examine or change gets exposed. Here's an example:

<script>
new MutationObserver((mutations, observer) => {
  // Find whether the script tag you want to tamper with exists
  // If you can't predictably identify its location,
  // you may have to iterate through the mutations' addedNodes
  const tamperTarget = document.querySelector('script + script');
  if (!tamperTarget) {
    return;
  }
  observer.disconnect();
  console.log('Target script getting tampered with');
  tamperTarget.textContent = tamperTarget.textContent.replace(
    'return function',
    'window.y = y; return function'
  );
  setTimeout(() => {
    console.log("Hacked into tamper target's script and found a y of", y);
    console.log('Could also have replaced the local y with another value');
  });
})
  .observe(document.body, { childList: true });

</script>

<script>
console.log('Tamper target script running');
var x = (function(){
   var y = 5;
   return function() {
       alert(y);
   };
})();
</script>

This may not have been what you had in mind, but this sort of method is one of the very few ways to hack into a closure, which is a useful technique when the page runs code you can't change.

If the other script has a src instead of inline code, it'll be a bit harder. When the <script> tag is seen by the MutationObserver, tweak it or replace it with a new script tag whose textContent is the original script contents plus your modifications. In order to get the original contents, either examine the built-in <script> and hard-code its replacement, or fetch the text of the script tag (possibly bouncing the request off another server to avoid CORS issues) before you can make the required replacements and insert the patched code. (Or, if you have a server, you could have the server do the text replacements - then, all you need to do is change the src of the inserted script tag to point to your server rather than the default location.)

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • interesting, can u elaboratet a bit on the "script + script" part? I noticed it wont work without it, I just dont know what it does – B''H Bi'ezras -- Boruch Hashem Apr 14 '20 at 07:47
  • That's just an example to differentiate the built-in page script tag from the script your extension/userscript/etc injects into the page. In reality, the upper script tag probably won't exist, since something else from the browser will be "running" that code. – CertainPerformance Apr 14 '20 at 07:52
  • Oh interesting, so the "+ script" just skips over the first one, right, didn't realize.. does the mutationobserver iterate over all scripts that are loading? In production should one instead use document.querySelectorAll, or is it not necessary? – B''H Bi'ezras -- Boruch Hashem Apr 14 '20 at 07:55
  • Oh, you mean the selector, that's how the injected upper script identifies whether the lower script exists. `script + script` is a CSS selector which will match a ` – CertainPerformance Apr 14 '20 at 08:00
  • You can use any method you want to identify the element you want. I like `querySelectorAll`, but anything which accomplishes the logic you're looking for will work – CertainPerformance Apr 14 '20 at 08:01
4

That's (one of) the purpose(s) of a closure - to keep information private. Since the function already has been executed its scope variables are no longer available from outside (and have never been) - only the functions executed in it's scope (still) have access.

However you could give access via getters/setters.

You might want to take a look into Stuart Langridge's talk about closures. Very recommendable are also Douglas Crockfords Explanations. You can do lots of fancy stuff with closures;)

Edit: You have several options to examine the closure: Watch the object in the webdeveloper console or (as I do it often) return a debug-function which dumps out all the private variables to the console.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Christoph
  • 50,121
  • 21
  • 99
  • 128
3

No, not unless you expose it:

var x = function(){
        var y = 5;

        return {             
           getY: function(){
              return y;
          },
          setY: function(newY){
             y = newY;
          }    
       }
   }


    x.setY(4);
Gho5t
  • 1,060
  • 1
  • 12
  • 23
2

You can edit the alert function:

var x = (function(){
   var y = 5;
   return function() {
       alert(y);
   };
})();

var oldAlert = alert;

alert = function (x) {
    oldAlert(x);
    window.y = x;
}

x();

console.log(y); // 5

Or if you own the code, you can use standart getters and setters.

jasssonpet
  • 2,079
  • 15
  • 18
  • 6
    Heh, + 1 for thinking outside the box. I think the OP wants to be able to inspect all variables in the closure though. – Matt Jun 25 '12 at 16:10
  • I just want to make the point, that closures aren't entirely black box. – jasssonpet Jun 25 '12 at 16:11