Javascript local static variable
Asked Answered
S

4

15

Not sure I completely understand answers to similar questions that I found here, so trying to be absolutely sure:

I would like to have a local variable in a function, initialized only once (similar to static variables in strongly-typed languages such as C, C++, etc).

Of course, I could declare it globally, but it seems better practice to have it within the scope of that function, since it is not used anywhere else.

Now, here is what I do:

function func(data) {
    func.PARAMS = [
        {"name": "from", "size": 160, "indexed": true},
        {"name": "input", "size": 256, "indexed": false},
        {"name": "output", "size": 256, "indexed": false},
    ];
    ...
}

And my question is, will func.PARAMS indeed be initialized only once, or will it be initialized every time the function is called?

According to some of the answers that I found (this one for example), I need to precede the initialization with something like:

if (typeof func.PARAMS == 'undefined')

This "supplemental" would be irrelevant in strongly-typed languages of course, so I just want to be sure that it is absolutely necessary in order to ensure "static behavior" (i.e., one-time initialization).

Sutton answered 22/2, 2018 at 8:47 Comment(1)
Yes, if you don't check typeof func.PARAMS == 'undefined', according to func, PARAMS would be set every time funct is called.Snowden
A
23

In addition to using properties of the function object, as you do in your example, there are 3 other ways to emulate function-local static variables in Javascript.

All of them rely on a closure, but using different syntax.

Method 1 (supported in old browsers):

var someFunc1 = (function(){
    var staticVar = 0 ;
    return function(){
        alert(++staticVar) ;
    }
})() ;

someFunc1() ; //prints 1
someFunc1() ; //prints 2
someFunc1() ; //prints 3

Method 2 (also supported in old browsers):

var someFunc2 ;
with({staticVar:0})
    var someFunc2 = function(){
        alert(++staticVar) ;
    } ;

someFunc2() ; //prints 1
someFunc2() ; //prints 2
someFunc2() ; //prints 3

Method 3 (requires support for EcmaScript 2015):

{
    let staticVar = 0 ;
    function someFunc3(){
        alert(++staticVar) ;
    }
}

someFunc3() ; //prints 1
someFunc3() ; //prints 2
someFunc3() ; //prints 3

Method 3 for strict mode:

'use strict'
{
    let staticVar = 0 ;
    var someFunc3 = function(){
        alert(++staticVar) ;
    } ;
}

someFunc3() ; //prints 1
someFunc3() ; //prints 2
someFunc3() ; //prints 3
Amphitheater answered 26/4, 2018 at 1:50 Comment(3)
I tried Method 3 and (as expected) got someFunc3() is undefined...Invest
@PeteLomax, you are probably using strict mode. See the answer update.Amphitheater
my mistake, deletedInvest
U
2

It would be assigned every time the function is called. There are no static variables in JavaScript. You need to declare them outside of the function. You can do that in a local scope, though:

var func;
{
    const PARAMS = [
        {"name": "from", "size": 160, "indexed": true},
        {"name": "input", "size": 256, "indexed": false},
        {"name": "output", "size": 256, "indexed": false},
    ];
    func = function(data) {
        …
    }
}
Unready answered 22/2, 2018 at 9:44 Comment(0)
C
0

While Javascript doesn't natively have the concept of static variables, it is easy to simulate them. One pattern with objects is to use a closure (via a self-invoking function).

const MyClass = ( function() {

    // Static variables are in the scope of the self-invoking function

    const _myStaticVariable = 'this is a static variable';
      let _instances = 0; // this is also a class variable

    // The constructor function is returned to MyClass, but still has the static variables in scope

    return function() {

        _instances++;

        this.sayStaticVariable = function(){
            console.log(_myStaticVariable);
        }

        this.howMany = function(){
            console.log(_instances);
        }
    }

})();

myInstance = new MyClass();

myInstance.sayStaticVariable();
// this is a static variable

myInstance.howMany();
// 1

In this case, _myStaticVariable and _instances will only be initialised once, but will still be in scope of (and accessible to) the constructor function returned to MyClass.

As you seem to be asking about static variables in the context of functions rather than objects, you could probably adapt this pattern using functional composition

Correspondent answered 22/2, 2018 at 10:27 Comment(0)
S
0

Soln (workaround) - a Util Class

-- create a _ Util Class _ & use its function as _ the desired function with local static variable _
-- (an general & abstract & ugly example)
-- (a trivial idea (easy to think of, but appears to be too cumbersome))

  • to use this:_

    • put your desire function in place of function (arg1, arg2) {

    • init your static var inside if (this.firstRun === true) {

    • run your desire function as mimicFunc_M.run(1, 2); does

  • ex code

    // this class mimic what a function does + run only once feature (-- kinda static)
    class MimicFuncWithStaticVar {
      firstRun = true;
    
      func;
    
      staticVar;
    
      // pass in the function you want to exec
      constructor(func) {
        this.func = func;
      }
    
      run(arg1, arg2) {
        this.func(arg1, arg2);
      }
    }
    
    // ex:
    let mimicFunc_M = new MimicFuncWithStaticVar(function (arg1, arg2) {
      if (this.firstRun === true) {
        console.log('first run, run only once, init your static variable here.');
        this.staticVar = 0;
        this.firstRun = false;
      }
      console.log('out: ' + (arg1 + arg2) + '; ' + 'staticVar: ' + this.staticVar);
      this.staticVar++;
    });
    
    mimicFunc_M.run(1, 2);
    mimicFunc_M.run(4, 2);
    mimicFunc_M.run(4, 6);
    
    // [Output]:
    // 
    // first run, run only once, init your static variable here.
    // out: 3; staticVar: 0
    // out: 6; staticVar: 1
    // out: 10; staticVar: 2
    

->>

Togger class (a specific realistic example)

  • above ex looks very ugly, seems not good

  • but there is a case where it can apply to -- a Toggler

    -- if your intention is to:_ search for a clean way to write a function with toggle ability

    -- ie: you dont want to put the let toggle = false; thing outside of a function, you want it to be "inside" the function

    (/ or a least inside some sort of scope -- eg: to avoid name collision -- especially when you have many toggle functions).

    -- I cant do that for a function, but I can encapsulate that in a Class (Toggler).

  • ex code (old version, the new version added this.toggleExec = this.toggleExec.bind(this); & actually use a function)

    class Toggler {
      toggle = false;
      func;
    
      constructor(func) {
        this.func = func;
      }
    
      toggleExec(tog_in = undefined) {
        if (tog_in === undefined) { //path-normal
          this.toggle = !this.toggle;
          this.func();
        } else {
          if (this.toggle === tog_in) { // toggle state match, do nothing
            console.log('toggle state match, do nothing')
          } else {
            this.toggle = !this.toggle;
            this.func();
          }
        }
      }
    }
    
    // ex:
    let toggler_M = new Toggler(function () {
      // ... do your business logic here
      console.log('out: ' + this.toggle);
    });
    toggler_M.toggleExec();
    toggler_M.toggleExec();
    toggler_M.toggleExec();
    toggler_M.toggleExec(true);
    toggler_M.toggleExec(true);
    toggler_M.toggleExec(true);
    toggler_M.toggleExec(false);
    toggler_M.toggleExec(false);
    toggler_M.toggleExec(true);
    
    // [Output]:
    // 
    // out: true
    // out: false
    // out: true
    // toggle state match, do nothing
    // toggle state match, do nothing
    // toggle state match, do nothing
    // out: false
    // toggle state match, do nothing
    // out: true
    
  • ex code

    class Toggler__test_bind {
      toggle = false;
      func;
    
      constructor(func) {
        this.func = func;
        // https://mcmap.net/q/158952/-39-this-39-is-undefined-in-javascript-class-methods
        this.toggleExec = this.toggleExec.bind(this); // <- Add this
      }
    
      toggleExec(tog_in = undefined) {
        if (tog_in === undefined) { //path-normal
          this.toggle = !this.toggle;
          this.func();
        } else {
          if (this.toggle === tog_in) { // toggle state match, do nothing
            console.log('toggle state match, do nothing')
          } else {
            this.toggle = !this.toggle;
            this.func();
          }
        }
      }
    
      // use this instead of the constructor
      static init(func) {
        return new Toggler__test_bind(func).toggleExec;
      }
    }
    
    // let func_tog2 = new Toggler__test_bind(function () {
    //   // ... do your business logic here
    //   console.log('out: ' + this.toggle);
    // }).toggleExec;
    // func_tog2();       // out: true
    // func_tog2();       // out: false
    // func_tog2();       // out: true
    // func_tog2(true);   // toggle state match, do nothing
    // func_tog2(true);   // toggle state match, do nothing
    // func_tog2(true);   // toggle state match, do nothing
    // func_tog2(false);  // out: false
    // func_tog2(false);  // toggle state match, do nothing
    // func_tog2(true);   // out: true
    
    let func_tog3 = Toggler__test_bind.init(function () {
      // ... do your business logic here
      console.log('out: ' + this.toggle);
    });
    func_tog3();       // out: true
    func_tog3();       // out: false
    func_tog3();       // out: true
    func_tog3(true);   // toggle state match, do nothing
    func_tog3(true);   // toggle state match, do nothing
    func_tog3(true);   // toggle state match, do nothing
    func_tog3(false);  // out: false
    func_tog3(false);  // toggle state match, do nothing
    func_tog3(true);   // out: true
    
    • @note:
      () => {} lambda expression wont work... must use function () {} explicitly...
      --ie: Toggler.init(() => { >> Toggler.init(function () {
      • (guess) something is wrong with this in the callback
      • maybe its better to explicitly pass the this.toggle to the func
Sentiment answered 29/1, 2023 at 20:48 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.