0

I'm a bit confused as to how scope is seen by/related to inner objects. I understand that scope defines how variables are enclosed/seen and that context referes to the creation and binding of this to an object, and that inner objects and functions have access to the global object; however, I don't understand how (or if) scope relates to objects.

  1. How does scoping work under the hood?
  2. Are the only ways to access an external variable in an object via (a) binding the this that contains the external variable to the object or (b) adding that variable to the global object? Or, do objects have references to external scopes using javascript scoping?

I'm trying to set up a NodeJS web server and want to define a local variable which can be accessed by all other inner objects that are constructed in the entry (index) file. I tried to do this with var x = 10; in the entry file; yet, when I try to access the variable from an object instantiated later on in the code, there is no way to access that variable. I instantiated an object using the new operator, then called a method in that object that logs x to the console, but I get an "x is not defined" error. When debugging in chrome, I can't find x defined anywhere. I see the global context, which appears to just be the global object, I see the local context, which appears to be the instantiated object, yet I see no reference to the context that instantiated the object. I can place the variable on the global object and then it can be referenced, but I don't want to fall into the bad habit of placing everything on the global object.

Top-Level (file called startup.js and is called using node startup.js):

  var express = require('express');
  var web = express();
  var Controller = require('./classes/Controller');
  var x = 10;
  var controller = new Controller(props);
  web.post('/addEntry', function(req, res){
    controller.createEntry(req.body, (error, status) =>{
      if (error) {
          res.send('Error: ' + error);
          console.log(error);
      }
      res.send("Success:" + status);
    });
 });

 web.listen(3000);

Controller file:

class Controller {
  constructor()

  createEntry(entry, callback) {
     console.log(x);
     return callback(entry);
  }
}

module.exports = Controller;
  1. I would like to be able to reference x from an inner scope in an established pattern that is considered good practice. If this isn't possible using variables in higher-level contexts than the methods in instantiated objects, I would really appreciate it if you could suggest an alternative (preferably that isn't attaching to the global object). Thank you for your time and help and please let me know if and how I can improve the post!
  • Scope is determined statically and is really not much different in JavaScript from other similar languages. The main thing that causes "scope" confusion is asynchronous APIs and associated callback function semantics. – Pointy Jan 07 '19 at 15:07

2 Answers2

1

The x in your example is only accessible to code within startup.js. It isn't accessible anywhere else. It isn't a global (which I think you already know), it's a module global within the startup.js module.

To make it accessible to Controller's code, the usual thing to do is to pass it to Controller as an argument:

var controller = new Controller(props, x);
// ------------------------------------^

That doesn't pass the variable, but it does pass the variable's current value as of when that line of code executes.

If you want to pass Controller something that will see the current value of x even if you change it, you'd put x in an object as a property, and pass the object reference to Controller instead:

var stuff = {x: 10};
// ...
var controller = new Controller(props, stuff);

Controller would remember the object reference, and then look up x on that object:

class Controller {
  constructor(props, stuff) {
      this.stuff = stuff; // <====
  }

  createEntry(entry, callback) {
     console.log(stuff.x);
     // ---------^^^^^^
     return callback(entry);
  }
}

module.exports = Controller;

Alternately, depending on what x is (a system-wide configuration parameter?): You could put x and other things related to it in their own module (in, say, stuff.js):

module.exports = {x: 10};

...and have controller.js and anything else that needed it load the module:

var stuff = require("./stuff");
// ...
class Controller {
  constructor(props) {
  }

  createEntry(entry, callback) {
     console.log(stuff.x);
     // ---------^^^^^^
     return callback(entry);
  }
}

I think the above answers questions #2 and #3, but:

1. How does scoping work under the hood?

Scoping is a lexical (text) concept that determines what identifiers, et. al., are accessible to code. Scopes work like nested boxes: Each scope has access to everything in its containing scope. So for instance:

var x;
// Code here has access to `x`
function outer() {
    var y;

    // Code here has access to `x` and `y`

    function inner() {
        var z;

        // Code here has access to `x`, `y`, and `z`
    }

    inner();
}

outer();

You might be thinking: "Hey, wait, but what if you call outer twice? Then there's two ys!" And you're absolutely right, there are! And also two inners. Each inner has access to the same x, but each has a different y: The y created for the call to outer that created inner.

This is how closures work. More:


Re this.x: You mentioned using this to access x (e.g., this.x). That mechanism (this.x) looks up a property on an object. In contrast, your x variable isn't a property, it's a variable. JavaScript objects and scope don't have a lot to do with one another. Although we often conceptualize scope as using hidden objects behind the scenes (the spec certainly does), those objects are purely conceptual and not accessible in code (with one exception: the global object, which doubles both as a real object and a container for global variables declared with var and global declared functions [but not for variables declared with let or const, or classes defined with class]).

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thanks for the info! Some follow up questions: Does this mean that objects have no access to any scope-related concepts except their own scope? And to pass any external information in you either have to bind a separate `this` to the object, access a property from the global object, or pass the information in as an argument? Are variables defined in the global scope automatically added as a property to the global object? Or is everything in the global scope accessible by the entire program? – Translucent Dragon Jan 07 '19 at 17:06
  • @TranslucentDragon - Don't think in terms of the *object*. Think in terms of the *functions* associated with the object. There are really just three ways functions can access information from outside themselves: 1. They close over a variable with the value (in which case they can read it, and write to it provided it's not a `const`). 2. You pass them the value as an argument (and the `this` you call them with is effectively an argument). 3. They call a function that has access to the value and which provides it to the calling function (as a return value, for instance). That's about it. – T.J. Crowder Jan 07 '19 at 17:37
  • (BTW, globals are just an example of #1: Everything closes over the global environment.) In all three cases, the value they access might be what they're looking for, or an reference to an object with a property containing the desired value, or with a property referring to another object containing the desired value, etc., etc. – T.J. Crowder Jan 07 '19 at 17:38
0

How does scoping work under the hood?

Scope is delimited by function (var) or block (let).

If the var is in the same function or let is in the same block, you can access it.

If your current scope is defined inside the scope where the variable is available, then you can access it unless it is masked by another variable of the same name in a narrower scope.

Are the only ways to access an external variable in an object via (a) binding the this that contains the external variable to the object or (b) adding that variable to the global object? Or, do objects have references to external scopes using javascript scoping?

Your case (a) would not access the variable. Object properties are not variables.

Your case (b) just means the variable is stored in the widest possible scope. Globals are best avoided. Keep scopes as narrow as possible. It makes it much easier to manage code.

I would like to be able to reference x from an inner scope in an established pattern that is considered good practice.

Pass the value of x as an argument to the function. Do not try to access x directly.

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335