简体   繁体   中英

Node Function Scope

I jumped into the deep end recently and have been slowly learning to swim. I'm working on a CLI for building out a simple text game world. That code is becoming a convoluted mess and so I have tried to recreate the error I am getting in a simpler form below.

Try as I might I can't seem to understand the best way to structure all of my functions. In my project I have a parser function that breaks input up and searches for a 'verb' to invoke via a try/catch block. When a verb ie 'look' runs it accesses my database module and sends a query based on several parameters to return the description of a room or thing. Because this is all asynchronous virtually everything is wrapped in a promise but I am leaving that out of this example. The following is not the actual project, just a simple recreation of the way I have my objects set up.

APP:

// ***********************
const player = require('./scope_test_player');

player.look();
player.water();

Module1:

// ***********************
const apple_tree = require('./scope_test_apple_tree');

module.exports = {
  look: function(){
    console.log(
      'The apple tree is '+apple_tree.height+'ft tall and has '
      +apple_tree.apples+' apples growing on it'
    );
  },
  water: function() {
    apple_tree.grow();
  }
};

Module2:

// ***********************
const player = require('./scope_test_player');

module.exports = {
  height: 10,
  nutrition: 0.3,
  apples: [],
  fertilize: function(number) {
    this.nutrition+=number;
  },
  grow: function() {
    this.height+=this.nutrition;
  }
};

In the above code I get 'TypeError: apple_tree.grow is not a function' from water or undefined from look. This is the bane of my existence and I have been getting this seemingly at random in my main project which leads me to believe I dont understand scope. I know I can require the module within the function and it will work, but that is hideous and would add hundreds of lines of code by the end. How do I cleanly access the functions of objects from within other objects?

You problem is that have a cyclic dependencies in your project and that you overwrite the exports property of the module. Because of that and the way node cachges required modules, you will get the original module.exports object in scope_test_player file and not the one you have overwritten. To solve that you need to write it that way:

// ***********************
const apple_tree = require('./scope_test_apple_tree');

module.exports.look = function() {
  console.log(
    'The apple tree is ' + apple_tree.height + 'ft tall and has ' + apple_tree.apples + ' apples growing on it'
  );
};

module.exports.water = function() {
  apple_tree.grow();
};

And

// ***********************
const player = require('./scope_test_player');

module.exports.height = 10;
module.exports.nutrition = 10;
module.exports.apples = [];
module.exports.fertilize = function(number) {
  this.nutrition = +number;
};

module.exports.growth = function() {
  this.height = +this.nutrition;
}

But this is a really bad design in gerenal and you should find another way how to solve that. You should always avoid loops/circles in your dependency tree.

UPDATE

In node each file is wrappted into load function in this way:

function moduleLoaderFunction( module, exports /* some other paramteres that are not relavant here*/)
{
   // the original code of your file
}

node.js internally does something like this for a require:

var loadedModules = {}

function require(moduleOrFile) {
    var resolvedPath = getResolvedPath(moduleOrFile)
    if( !loadedModules[resolvedPath] ) {
       // if the file was not loaded already create and antry in the loaded modules object
       loadedModules[resolvedPath] = {
         exports : {}
       }

       // call the laoded function with the initial values
       moduleLoaderFunction(loadedModules[resolvedPath], loadedModules[resolvedPath].exports)
    }

    return loadedModules[resolvedPath].exports
}

Because of the cyclic require, the require function will return the original cache[resolvedPath].exports , the one that was initially set before you assinged your own object to it.

Is Module1 = scope_test_player and Module2 = scope_test_apple_tree? Maybe you have a cyclic reference here?

APP requires scope_test_player

// loop

scope_test_player requires scope_test_apple_tree

scope_test_apple_tree requires scope_test_player

// loop

As I can see scope_test_apple_tree doesn't use player.

Can you try to remove:

const player = require('./scope_test_player');

from Module2 ?

There are a few issues to address.

Remove the player require in Module 2( scope_test_apple_tree.js ):

const player = require('./scope_test_player')

It doesn't do any damage keeping it there but it's just unnecessary.

Also, replace =+ with += in fertilize and grow which is what I think you are going for.

I was able to run the code natually with those fixes.


If you want to refactor, I'd probably flatten out the require files and do it in the main file controlling the player actions and explicitly name the functions with what is needed to run it (in this case...the tree).

Keeping mostly your coding conventions, my slight refactor would look something like:

index.js

const player = require('./scope_test_player');
const apple_tree = require('./scope_test_apple_tree');

player.lookAtTree(apple_tree);
player.waterTree(apple_tree);

scope_test_player.js

module.exports = {
  lookAtTree: function(tree){
    console.log(
      'The apple tree is '+tree.height+'ft tall and has '
      +tree.apples.length+' apples growing on it'
    );
  },
  waterTree: function(tree) {
    tree.grow();
    console.log('The apple tree grew to', tree.height, 'in height')
  }
};

scope_test_apple_tree.js

module.exports = {
  height: 10,
  nutrition: 0.3,
  apples: [],
  fertilize: function(number) {
    this.nutrition += number;
  },
  grow: function() {
    this.height += this.nutrition;
  }
};

Yes, I had circular dependencies in my code because I was unaware of the danger they imposed. When I removed them from the main project sure enough it started working again. It now seems that I'm going to be forced into redesigning the project as having two modules randomly referencing each other is going to cause more problems.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM