0

I want to reuse the function sayMyName but with different variables. Please let me know if I'm structuring this the wrong way and what is the best practice what what I'm trying to do.

var sayMyName = function(myName) {
  console.log(myName)
};

var name1 = function() {
  // myName should not be a global variable 
  // because there may be more variables/functions 
  // that I'd want to closed inside sayMyName(). 
  // Declaring all of them to the global scope is not ideal.
  var myName = 'Walter';

  sayMyName();
  // I don't want to pass in myName as argument like this:
  // sayMyName(myName);
  // I want myName to be implicitly included in sayMyName()
  // I want to pass in everything that is declared in name1 to sayMyName() implicitly.
};

var name2 = function() {
  var myName = 'White'; 
  sayMyName();
}

name1(); // should give me 'Walter'
name2(); // should give me 'White'
chh
  • 850
  • 1
  • 9
  • 11
  • To use a [closure](http://stackoverflow.com/questions/111102/how-do-javascript-closures-work?rq=1) you have to put `sayMyName` inside the `name1` function – Bergi Jul 01 '16 at 00:49

3 Answers3

2

I'm not sure why you specifically want a closure, but by looking at your example it seems that a bind would be more appropriate than a closure.

var sayMyName = function(myName) {
  console.log(myName)
};

var name1 = sayMyName.bind(undefined, 'Walter');
var name2 = sayMyName.bind(undefined, 'White');

name1(); // log 'Walter'
name2(); // log 'White'
user3
  • 740
  • 3
  • 15
  • I guess what I'm trying to do is take the clousure function out of the enclosing context and be able to assign it to a variable, and call that variable instead. – chh Jul 01 '16 at 00:27
1

Move your variable myName to the outermost scope:

var myName;

var sayMyName = function() {
  console.log(myName)
};

var name1 = function() {
  myName = 'Walter';
  sayMyName();
};

var name2 = function() {
  myName = 'White'; 
  sayMyName();
}

name1(); // should give me 'Walter'
name2(); // should give me 'White'

Update: Thinking about it, if you're willing to use the non-standard, Error.stack attribute, are willing to use named functions, and are willing to use a naming convention, you could hackishly achieve your goal:

function sayMyName() {
  try {
    throw new Error();
  }
  catch (e) {
    if (e.stack) { // non-standard attribute
      var reNames = /^\s*at myNameIs([A-Z][^(\s]*)\s*\(/mg;
      reNames.lastIndex = 0;

      var buffer = [];
      for (var match = reNames.exec(e.stack); null !== match; match = reNames.exec(e.stack)) {
        buffer.push(match[1]);
      }

      console.log(buffer.join(" "));
    }
  }
}

function myNameIsWalter() {
  sayMyName();
}

function myNameIsWhite() {
  myNameIsWalter();
};

myNameIsWalter();  // "Walter"
myNameIsWhite();   // "Walter White"

... and if you're willing to use eval (bad !!!), then you could do fancier things like the following:

var sayMyName = function () {
  try {
    throw new Error();
  }
  catch (e) {
    if (e.stack) { // non-standard attribute
      var reNames = /^\s*at ([_a-zA-Z][_a-zA-Z0-9]+(\.[_a-zA-Z][_a-zA-Z0-9]+)*)/mg;
      reNames.lastIndex = 0;

      var reMyName = /\bmyName\s*=\s*(?:"([^"]*)"|'([^']*)')/g;

      var identifier, definition, match, myName, buffer = [];
      while (null !== (match = reNames.exec(e.stack))) {
        try {
          identifier = match[1];
          if ("sayMyName" !== identifier) {
            definition = eval(match[1] + '.toString()');
            if (/\bsayMyName\(\)/.test(definition)) {
              reMyName.lastIndex = 0;
              buffer.length = 0;
              while (null !== (myName = reMyName.exec(definition))) {
                buffer.push(myName[1]);
              }
              console.log(buffer.join(" "));
            }
          }
        }
        catch (_) {
          // continue
        }
      }
    }
  }
};

function name1() {
  var myName = "Walter";
  sayMyName();
}

function name2() {
  var myName;
  myName = "Walter";
  myName = "White";
  sayMyName();
}

name1(); // "Walter"
name2(); // "Walter White"

You could even use the non-standard, Function.caller attribute, which is probably the cleanest approach (if it works in your browser -- it's non-standard for a reason):

function sayMyName() {
  var reMyName = /\bmyName\s*=\s*(?:"([^"]*)"|'([^']*)')/g;

  var definition, buffer = [];
  for (var caller = sayMyName.caller; caller; caller = caller.caller) {
    definition = caller.toString();
    if (/\bsayMyName\(\)/.test(definition)) {
      reMyName.lastIndex = 0;
      buffer.length = 0;
      while (null !== (myName = reMyName.exec(definition))) {
        buffer.push(myName[1]);
      }
      console.log(buffer.join(" "));
    }
  }
};

function name1() {
  var myName = "Walter";
  sayMyName();
}

function name2() {
  var myName;
  myName = "Walter";
  myName = "White";
  sayMyName();
}

name1(); // "Walter"
name2(); // "Walter White"

This is arguably no less code than just passing a parameter, though.

Dylon
  • 1,730
  • 15
  • 14
  • 1
    I'm trying to avoid global variable. What if I have a few other variables/functions that I also want them to be closed in sayMyName? I don't want to declare all of them to the global scope. – chh Jul 01 '16 at 00:16
  • You don't have another option unless you want to pass them in as parameters. If you have a lot of parameters, then I'd suggest passing them in as a mapping. For example, rather than having function(a,b,c,d,e,f,g,h,i,j), you would have function(params), where params is an object with keys a,b,c,d,e,f,g,h,i,j. – Dylon Jul 01 '16 at 00:21
  • JavaScript doesn't support dynamic scoping, so there's no way for sayMyName to look up the call stack for the value of myName. – Dylon Jul 01 '16 at 00:37
  • Good idea, packaging everything I need to pass in an object. Just wondering is there any other better way to do this. Maybe I'm looking at the problem the wrong way... – chh Jul 01 '16 at 00:38
  • 1
    It sounds like you want to use dynamic scope resolution, which is popular in some languages. JavaScript doesn't support dynamic scoping, though, only lexical scoping: http://c2.com/cgi/wiki?DynamicScoping – Dylon Jul 01 '16 at 00:40
  • I see. For Javascript if I want closure I need to write the function in the same scope. In this case, I have to write the sayMyName function twice, inside name1 and name2. This duplication is what I'm trying to avoid. In terms of best practice, which one is better? Passing an object to the external function or writing the function twice? – chh Jul 01 '16 at 00:42
  • I thought about it, and realized you could use the Error.stack attribute to achieve your goal. See my solution for an example. – Dylon Jul 01 '16 at 01:16
  • 1
    Thanks Dylon. There is a bit too much going on in there for me to understand at my current level. Since I'm still learning, I try to stick with best practices and avoid workarounds. – chh Jul 01 '16 at 01:55
  • Good idea. I'm having fun, though! I have one more :) – Dylon Jul 01 '16 at 02:05
0

If you want closure then this is example.

function sayMyName(myName){
   return function(){
      console.log(myName); //myName is available from parent scope
      return myName; 
   }
}
var name1 = sayMyName('Walter');
var name2 = sayMyName('White');
//no console output by now
name1(); //Walter
name2(); //White
//myName is not available in global scope
console.log(myName); //undefined 
Alex Kudryashev
  • 9,120
  • 3
  • 27
  • 36