开发者

Have a javascript function privately track it's number of calls

I'm trying to figure out how I can have a javascript function privately track the number of times it has been called. The objective is to be able to query this value in the console during debugging by doing func.run

My first attempt:

function asdf() { 
  if (!asdf.run) {
    开发者_运维百科asdf.run = 0;
  } else {
    asdf.run++;
    console.error('run: ' + asdf.run);
  }
  console.error('asdf.run def: ');
  console.error(asdf.run);
}
asdf();


This is a good lesson of why one should ALWAYS aim to use === in nearly all javascript booleans, cause they could secretly be ==


Closures are the way to go here:

var asdf = (function () {
    var runs = 0;
    var f = function () {
        ++runs;
        // your function here
    };
    f.runs = function () {
        return runs;
    };
    return f;
}());

Usage:

asdf();
asdf();
asdf.runs(); // 2
asdf();
asdf.runs(); // 3

Or, you could use a mocking framework like (shameless self plug) Myrtle.


Your first try would work fine except you've forgotten that 0 is a "falsy" value in JavaScript, so on the first run and every run thereafter !this.run will evaluate to true and your else block will never be reached. This is pretty easy to work around.

function foo() {
  if(typeof(foo.count) == 'undefined') {
    foo.count = 0;
  } else {
    foo.count++;
  }
  console.log(foo.count);
}

foo(); # => 0
foo(); # => 1
foo(); # => 2
# ...


I haven't actually tried this, but I looked up "static function variables in JavaScript", and I found this resource. I think the main difference between what you wrote and what's in that solution is how the first run of the function is detected. Perhaps your !asdf.run test is not working the way you thought it was, and you should use typeof asdf.run == 'undefined' to test instead.


OK, here is a method that I came up with that does not require the function to be modified at all.

So if you have this.

function someFunction() {
   doingThings();
}

you could add a counter like this...

addCounter(this, "someFunction");

where this is the scope you are in, you could use any object that has a method you want to count.

Here's the code for it.

<html>
<head>
    <script>
        function someFunc() {
            console.log("I've been called!");
        };

        // pass an object, this or window and a function name
        function wrapFunction(parent, functionName) {
            var count = 0, orig = parent[functionName];
            parent[functionName] = function() {
                count++;
                return orig.call(this, Array.prototype.slice(arguments));
            }

            parent[functionName].getCount = function() {
                return count;
            };
        }

        var someObj = {
            someFunc: function() {
                console.log("this is someObj.someFunc()");
            }                               
        }                                   


        wrapFunction(this, "someFunc");
        wrapFunction(someObj, "someFunc");
        someFunc();           

        someObj.someFunc();
        someObj.someFunc();
        someObj.someFunc();

        console.log("Global someFunc called " + someFunc.getCount() + " time" + (someFunc.getCount() > 1 ? "s" : "")) ;
        console.log("Global someObj.someFunc called " + someObj.someFunc.getCount() + " time" + (someObj.someFunc.getCount() > 1 ? "s" : "")) ;
    </script>                                                                   
</head>                                                                         


So, !asdf.run is a form of the double equals operator == and I had set asdf.run to 0 so it was false.

Using the triple equals === :

typeof asdf.run === "undefined" for the boolean solves my issue.

So a final usable and useful version:

function sdf() { 
  if (typeof sdf.run === "undefined") { sdf.run = 0; }
  sdf.run++;
}

To query the number of times sdf has been called:

sdf.run;

To actually make this variable private and protect it from change, one would implement a closure.


//using a closure and keeping your functions out of the global scope 
var myApp = (function() {
   //counter is a private member of this scope
   var retObj = {}, counter = 0;
   //function fn() has privileged access to counter
   retObj.fn = function() {
       counter++;
       console.log(counter);
    };
    //set retObj to myApp variable
    return retObj;
}());


myApp.fn(); //count = 1
myApp.fn(); //count = 2
myApp.fn(); //count = 3


You don't necessarily need a closure. Just use a static variable.

var foo = function(){
    alert( ++foo.count || (foo.count = 1) );
}


// test
function callTwice(f){ f(); f(); }
callTwice(foo)                  // will alert 1 then 2

or

callTwice( function bar(){           
    alert( ++bar.count || (bar.count = 1) );
});                             // will alert 1 then 2

the second one is a named anonymous function. And note this syntax:

var foo = function bar(){ /* foo === bar in here */ }
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜