1

Note: this question is a follow up of the recently asked question JavaScript: automatic getters and setters in closures without eval? .

The gist of that question was as follows: "How can one automatically provide getters and setters for scoped variables in a closure - without the use of the eval statement". There the poster, provided code demonstrating how to do so with eval and the user gave the following answer which does not require eval:

function myClosure() {
  var instance = {};
  var args = Array.prototype.slice.call(arguments);
  args.forEach(function(arg) {
    instance[arg] = function(d) {
      if (!arguments.length) return arg;
      arg = d;
      return instance;
    };
  })
  return instance;
};

This question is about how to have default values for the scoped variables which are to be set / get with the above function.

If we simply add a default value to the variable v3 we get the following:

function myClosure() {
  var v3 = 2
  var instance =  {};
  var args = Array.prototype.slice.call(arguments);
  args.forEach(function(arg) {
    instance[arg] = function(d) {
      if (!arguments.length) return arg;
      arg = d;
      return instance;
    };
  })
  return instance;
}

var test = myClosure("v1", "v2", "v3") // make setters/getters for all vars
test.v1(16).v2(2) // give new values to v1, v2
console.log(test.v1() + test.v2() + test.v3()) // try to add with default v3
// 18v3

I was not expecting that.

So how can I provide a default value to the variables?

Note: please build off the following implementation which generates the getters / setters on initialization (allowing the code author to pre-define all variables which should have getters and setters)

function myClosure() {
  var instance =  function () {};
  var publicVariables =['v1', 'v2', 'v3']
  function setup() {
    var args = Array.prototype.slice.call(arguments);
    // if called with a list, use the list, otherwise use the positional arguments
    if (typeof args[0] == 'object' && args[0].length) { args = args[0] }
    args.forEach(function(arg) {
      instance[arg] = function(d) {
        if (!arguments.length) return arg;
        arg = d;
        return instance;
      };
    })
  }
  setup(publicVariables)
  // setup('v1', 'v2', 'v3')  also works 
  return instance;
}

var test = myClosure()
test.v1(16).v2(2)
console.log(test.v1() + test.v2() + test.v3())

Question:

How to use default values in this set up (above code block) with automatic getters and setters?

Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171
SumNeuron
  • 4,850
  • 5
  • 39
  • 107
  • FWIW, it's very hard to follow your question, as (with apologies!) it rambles on a bit. (I see someone's downvoted it, this may be why.) Keeping questions concise and to the point helps people help you. – T.J. Crowder Mar 25 '18 at 09:41
  • 1
    @T.J.Crowder Ill try to clear it up. It is partly because this question is a follow-up to a previous question and answer. In short, the goal is to have a closure, with some scoped variables with default values. In addition, for these variables, automatically produce getters and setters – SumNeuron Mar 25 '18 at 09:43
  • How is "closure" defined? I find that term to odd, especially given its other more common meaning. – H.B. Mar 25 '18 at 09:53
  • @H.B. the definition can be a bit confusing. I am using that laid out by [W3Schools](https://www.w3schools.com/js/js_function_closures.asp), i.e. a function closure (where a variable is scoped inside a function and can be updated calling functions of the function closure. – SumNeuron Mar 25 '18 at 09:55
  • @H.B.: [This question's answers](https://stackoverflow.com/questions/111102/how-do-javascript-closures-work) and [my blog post (with slightly-outdated terminology now)](http://blog.niftysnippets.org/2008/02/closures-are-not-complicated.html) should help. (Edit: Ah, that's the meaning you expected. :-) ) – T.J. Crowder Mar 25 '18 at 09:56
  • Oh, so it is referring to the definition i expected. What is the point of this though? – H.B. Mar 25 '18 at 09:56
  • @H.B. if one defines a closure that others might use (e.g. an extension of `d3`) with a lot of public variables, copy-pasting the getters/setters is tedious (hence automatic part) and often, these variables require default values (hence this question on ensuring they can be set). See the linked original post for a function that does this (albeit with `eval`) which some have adverse feelings towards. – SumNeuron Mar 25 '18 at 09:58
  • I still don't get why you require the use of closures when you can just store the value on some object instead. – H.B. Mar 25 '18 at 10:19
  • @H.B. closures and objects are similar, but closure can enforce some safety as one can not add functionality to closures after they are initiated ( I think) whereas anyone can add anything to an object? Also, some libraries are built around closure (d3 for example) so for compatibility (other libraries e.g. bootstrap use objects instead) – SumNeuron Mar 25 '18 at 10:24

3 Answers3

2

The gist of that question was as follows: "How can one automatically provide getters and setters for scoped variables in a closure - without the use of the eval statement". There the poster, provided code demonstrating how to do so with eval and the user gave an answer which does not require eval.

No, you cannot do without eval. All the answers here that don't use any form of eval do not access scoped variables, but rather just plain properties - or they create their own local variables.

Providing a default value is rather simple with that:

function myClosure(...args) {
  var instance =  {v3: 2};
//                 ^^^^^ not a `var`
  for (const arg of args) {
    let val = instance[arg];
    instance[arg] = function(d) {
      if (!arguments.length) return val;
      val = d;
      return instance;
    };
  }
  return instance;
}
Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • +1 I appreciate your answer, the clear answer that what I exactly requested is not feasible without eval is not possible, and an answer to demonstrate default values in your solution. Ideally instance (what is being returned) is a function that does something on the variables which one is allowed to change from the default values. With your expertise, is there an approach (amongst the answer here or otherwise) you recommend for doing so? – SumNeuron Mar 25 '18 at 11:21
  • @SumNeuron How would you use that function? `test("v1", 16)`, `test("v3") == 2`? – Bergi Mar 25 '18 at 12:09
  • Assume the public variables (in our examples, v1, v2, v3) are parameters of the function which `myClosure` returns, e.g. `function(){ return (v1 + v2) % v3 })` – SumNeuron Mar 25 '18 at 12:16
  • @SumNeuron In that example they are free variables, not parameters? Did you mean `function(v1, v2, v3) { return (v1 + v2) % v3 }`? – Bergi Mar 25 '18 at 12:22
  • nope, i meant as free variables scoped under `myClosure`. Given the examples provided here, they would like have to be `(instance.v1() + instance.v2()) % instance.v3())`, hence my preference towards having them scoped in the closure – SumNeuron Mar 25 '18 at 12:23
  • @SumNeuron Yes, that's not possible, as that would require them to be local variables in the `myClosure`. (Maybe it's possible to do some trickery with `with` statements or stuff, but I wouldn't recommend it). – Bergi Mar 25 '18 at 13:11
1

Do you mean something like this:

function myClosure(...vars) {
  const instance =  {};
  vars.forEach(varArg => {
    let name = undefined;
    let value = undefined;
    if (typeof varArg == 'string')
    {
       name = varArg;
    }
    else
    {
       name = Object.keys(varArg)[0];
       value = varArg[name];
    }
 
    instance[name] = function(d) {
      if (!arguments.length) return value;
      value = d;
      return instance;
    };
  })
  return instance;
}

const test = myClosure(
  { "v1": 1 },
  "v2",
  { "v3": 3 },
);
// Print some defaults.
console.log(test.v1());
console.log(test.v2());

test.v1(16).v2(42) // give new values to v1, v2
console.log(test.v1(), test.v2(), test.v3())

Proxies, for the heck of it.

function myClosure(...vars) {
  const instance = vars.reduce((obj, { name, value }) => {
    obj[name] = value;
    return obj;
  }, {});

  let proxy;
  const handler = {
    get: function(target, prop) {
      return (...args) => {
        if (args.length == 0)
          return instance[prop];

        instance[prop] = args[0];

        return proxy;
      };
    }
  };

  proxy = new Proxy(instance, handler);
  return proxy;
}

const test = myClosure(
  { name: "v1", value: 1 },
  { name: "v2" },
  { name: "v3", value: 3 }
);
// Print some defaults.
console.log(test.v1());
console.log(test.v2());
console.log(test.vNew());

test.v1(16).v2(42).vNew(50); // give new values to some variables.
console.log(test.v1(), test.v2(), test.v3(), test.vNew())
H.B.
  • 166,899
  • 29
  • 327
  • 400
  • sort of. However now all values have to have defaults? So there is no way to have a `var` statement (e.g. above the `instance = ...` statement) – SumNeuron Mar 25 '18 at 10:07
  • @SumNeuron: The value will just be undefined at first if you provide no default. What do you mean by var statement? (Removed one of the defaults in the code.) – H.B. Mar 25 '18 at 10:08
  • Yeah that is much closer to what I meant. I think I am aiming for this though (https://jsfiddle.net/SumNeuron/qw0uqL28/11/ ) please take a look. I'll give it a day for others to post ideas before marking an answer. As for var statements, I meant just that e.g. `function myClosure() { var v1, v2=3, v3 ...}` – SumNeuron Mar 25 '18 at 10:26
  • Incidentally just adjusted the code to match the call signature of your linked example. – H.B. Mar 25 '18 at 10:46
  • I saw, very swanky. Would up-vote again if I could. You're really good at programming. Much cleaner than my original post using `eval` on a string. – SumNeuron Mar 25 '18 at 10:49
  • can I ask where you learned J.S. ? any suggestions of where to learn a more "proper" understanding than the light intro provided from w3schools. – SumNeuron Mar 25 '18 at 10:52
  • @SumNeuron: Essentially learning by doing. Solving lots of practical problems that presented themselves on their own. Every time i did not understand or know something i would google it, read SO, docs on MDN or the specs if need be. Also, would not recommend w3schools, they try to associate themselves with the w3, but really are not related at all. They also used to spread misinformation. – H.B. Mar 25 '18 at 10:56
  • @SumNeuron: I think codeacademy and khanacademy are fairly decent learning platforms. But a deep knowledge of languages probably comes from being exposed to tons of sometimes obscure issues over time. – H.B. Mar 25 '18 at 11:00
0

Note: I am posting my own answer for reference only. I will not be marking this as the answer to the question.

Building off the answer provided by @H.B., I update the answer in the following ways:

  1. getters and setters are made on initialization of the closure itself
  2. make the getter and setter production function a bit more messy to allow for more lazy definition of variables
  3. instance is now a function, not an object

    function myClosure() {
    
      var instance =  function () {
          console.log(this.v1(), this.v2(), this.v3())
      };
    
      var publicVariables =[ 'v1', 'v2', {'v3': 3} ] 
    
      function setup() {
    
    
        var args = Array.prototype.slice.call(arguments);
    
        // if called with a list, use the list, 
        // otherwise use the positional arguments
    
        if (typeof args[0] == 'object' && args[0].length) { args = args[0] }
    
        args.forEach(function(arg) {
            var name, value
            if(typeof arg == 'object') {
            name = Object.keys(arg)[0]
            value = arg[name]
          } else {
            name = arg
            value = undefined
          }
    
          instance[name] = function(d) {
            if (!arguments.length) return value;
            value = d;
            return instance;
          }; // end instance function
        }) // end for each
      } // end setup
      setup(publicVariables)
      return instance; 
    }
    
    var test = myClosure().v1(10).v2(2)
    console.log(test.v1(), test.v2(), test.v3())
    test.v1(20).v3(1)
    console.log(test.v1(), test.v2(), test.v3())
    
SumNeuron
  • 4,850
  • 5
  • 39
  • 107