15

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
8
  • Code sample would better explain your exact problem Commented Jun 25, 2012 at 16:01
  • The problem is theoretical, not practical : given a function, can you enumerate the properties of its closure ? Commented Jun 25, 2012 at 16:02
  • 1
    @Blaster: Try it. It won't work. y isn't a property on x. Commented Jun 25, 2012 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. Commented Jun 25, 2012 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 Commented Jun 25, 2012 at 16:06

4 Answers 4

7

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.)

Sign up to request clarification or add additional context in comments.

5 Comments

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
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.
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?
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 <script> tag which is immediately followed by another <script> tag. does the mutationobserver iterate over all scripts that are loading? Only one script can be in the process of being created at a time. The observer will run its callback right after a script is loaded, after it's been put into the DOM, right before the script executes.
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
5

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.

Comments

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);

Comments

3

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.

2 Comments

Heh, + 1 for thinking outside the box. I think the OP wants to be able to inspect all variables in the closure though.
I just want to make the point, that closures aren't entirely black box.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.