【问题标题】:How can I count the instances of an object?如何计算对象的实例?
【发布时间】:2012-09-04 22:14:04
【问题描述】:

如果我有一个 Javascript 对象定义为:

function MyObj(){};

MyObj.prototype.showAlert = function(){
   alert("This is an alert");
   return;
};

现在用户可以这样称呼它:

var a = new MyObj();
a.showAlert();

到目前为止一切顺利,还可以在同一代码中运行另一个实例:

var b = new MyObj();
b.showAlert();

现在我想知道,如何保存 MyObj 的实例数? 有内置功能吗?

我想到的一种方法是在 MyObj 初始化时增加一个全局变量,这将是跟踪该计数器的唯一方法,但有什么比这个想法更好的吗?

编辑:

在此处查看此建议:

我的意思是如何让它回到 2 而不是 3

【问题讨论】:

  • 当对象超出范围时会发生什么?
  • 不要将全局变量添加为计数器,将属性添加到MyObj.prototypeMyObj,或通过闭包添加私有变量。 (这些都没有解决 Lee Taylor 提到的问题。)

标签: javascript


【解决方案1】:

没有内置的;但是,您可以让您的构造函数记录它被调用的次数。不幸的是,JavaScript 语言无法判断对象何时超出范围或已被垃圾回收,因此您的计数器只会上升,而不会下降。

例如:

function MyObj() {
  MyObj.numInstances = (MyObj.numInstances || 0) + 1;
}
new MyObj();
new MyObj();
MyObj.numInstances; // => 2

当然,如果你想防止篡改计数,那么你应该通过闭包隐藏计数器并提供一个访问器函数来读取它。

[编辑]

根据您更新的问题 - 无法跟踪实例何时不再使用或“删除”(例如通过将 null 分配给变量),因为 JavaScript 没有为对象提供 finalizer methods

您可以做的最好的事情是创建一个“dispose”方法,当对象不再处于活动状态时(例如,通过reference counting 方案)将调用该方法,但这需要程序员的合作 - 语言不提供帮助:

function MyObj() {
  MyObj.numInstances = (MyObj.numInstances || 0) + 1;
}
MyObj.prototype.dispose = function() {
  return MyObj.numInstances -= 1;
};
MyObj.numInstances; // => 0
var a = new MyObj();
MyObj.numInstances; // => 1
var b = new MyObj();
MyObj.numInstances; // => 2
a.dispose(); // 1 OK: lower the count.
a = null;
MyObj.numInstances; // => 1
b = null; // ERR: didn't call "dispose"!
MyObj.numInstances; // => 1

【讨论】:

  • 但是:var x = new MyObj(); x = null; alert(MyObj.numInstances);
  • @nnnnnn:是的,这个简单的策略不会跟踪对象实例的生命周期,因此即使对象符合垃圾回收条件,计数器也不会递减。 (请注意,我自己的示例演示了这两个匿名实例,即使它们不可访问,仍然会被计算在内。)
  • 如果您添加一个小注释,即它是许多 created 实例 - 那么没有人会试图找到任何缺点;-)
  • 哦,完美,这只是我需要做的,实际上我不想在外面计数,而是在对象本身,非常感谢!!!
  • 哦哦,实际上我遇到了和@nnnnnn 建议的问题一样的问题,所以有没有办法解决这个问题,请再次检查我的问题
【解决方案2】:

在名为 countMyObj 构造函数上创建一个静态属性,并在构造函数本身内递增它。

function MyObj() {
    MyObj.count++;
}

MyObj.count = 0;

var a = new MyObj;
var b = new MyObj;

alert(MyObj.count);

这就是你通常在 Java 中执行的方式(使用静态属性)。

【讨论】:

  • 我不确定这是否是非常可靠的方法。您假设 MyObj 可以从全局空间访问。情况可能并非总是如此。
  • 我不明白你的意思。范围与此解决方案有什么关系?
【解决方案3】:
var User = (function() {
   var id = 0;
   return function User(name) {
      this.name = name;
      this.id = ++id;
   }
})();

User.prototype.getName = function() { 
    return this.name; 
}

var a = new User('Ignacio');
var b = new User('foo bar');

a
User {name: "Ignacio", id: 1}
b
User {name: "foo bar", id: 2}

【讨论】:

    【解决方案4】:

    使用ES6 Classes MDN 语法——我们可以定义一个static 方法:

    static 关键字定义了一个的静态方法。静态方法在没有实例化它们的类的情况下被调用,并且不能通过类实例调用。静态方法通常用于为应用程序创建实用函数。

    class Item {
    
      static currentId = 0;    
      _id = ++Item.currentId;  // Set Instance's this._id to incremented class's ID
                               // PS: The above line is same as:
                               // constructor () { this._id = ++Item.currentId; }
      get id() {               
        return this._id;       // Getter for the instance's this._id
      }
    }
    
    const A = new Item(); // Create instance (Item.currentId is now 1)
    const B = new Item(); // Create instance (Item.currentId is now 2)
    const C = new Item(); // Create instance (Item.currentId is now 3)
    
    console.log(A.id, B.id, C.id);                     // 1 2 3
    console.log(`Currently at: ${ Item.currentId }`);  // Currently at: 3

    PS:如果您不想记录-公开内部currentId 属性,请将其设为私有

    static #currentId = 0; 
    _id = ++Item.#currentId;
    

    这是一个带有 constructor 且没有 getter 的示例:

    class Item {
    
      static id = 0;
      
      constructor () {
        this.id = ++Item.id;
      }
    
      getID() {               
        console.log(this.id);
      }
    }
    
    const A = new Item(); // Create instance (Item.id is now 1)
    const B = new Item(); // Create instance (Item.id is now 2)
    const C = new Item(); // Create instance (Item.id is now 3)
    
    A.getID();   B.getID();   C.getID();       // 1; 2; 3
    console.log(`Currently at: ${ Item.id }`); // Currently at: 3

    【讨论】:

      【解决方案5】:

      这样的方法呢?

      var Greeter = (function ()
      {
          var numInstances;
      
          function Greeter(message)
          {
              numInstances = (numInstances || 0) + 1;
              this.greeting = message; 
          }
      
          Greeter.prototype.greet = function ()
          {
              return "Hello, " + this.greeting;
          };
      
          Greeter.prototype.getCounter = function ()
          {
              return numInstances;
          };
      
          return Greeter;
      
      })();
      
      var greeter = new Greeter("world");
      greeter.greet();
      greeter.getCounter();
      
      var newgreeter = new Greeter("new world");
      newgreeter.greet();
      newgreeter.getCounter();
      
      greeter.getCounter();           
      

      【讨论】:

        【解决方案6】:

        保持全局计数变量并每次递增是一种选择。另一种选择是在手动创建每个实例后调用counter 方法(我能想象到的最糟糕的事情)。但是还有另一个更好的解决方案。
        每次我们创建一个实例时,都会调用构造函数。问题是正在为每个实例创建构造函数,但我们可以在 __proto__ 内有一个 count 属性,每个实例可以是相同的。

        function MyObj(){
            MyObj.prototype.addCount();
        };
        
        MyObj.prototype.count = 0;
        
        MyObj.prototype.addCount = function() {
            this.count++;
        };
        
        var a = new MyObj();
        var b = new MyObj();
        

        毕竟这是我们的 ab 变量:

        【讨论】:

          【解决方案7】:

          最终,JS 将具有内置的代理功能,它将对后台发生的各种事情进行低级访问,永远不会暴露给前端开发人员(除了通过代理 - - 想想 PHP 等语言中的魔法方法。

          那时,只要跨平台 100% 保证支持销毁/垃圾收集作为触发器,在您的对象上编写一个使计数器递减的析构函数方法可能完全是微不足道的。

          目前可靠地做到这一点的唯一方法可能是创建一个包含所有已创建实例的封闭注册表,然后手动销毁它们(否则,它们将永远不会被垃圾收集)。

          var Obj = (function () {
              var stack = [],
          
                  removeFromStack = function (obj) {
                      stack.forEach(function (o, i, arr) {
                          if (obj === o) { arr.splice(i, 1); }
                          makeObj.count -= 1;
                      });
                  };
          
              function makeObj (name) {
                  this.sayName = function () { console.log("My name is " + this.name); }
                  this.name = name;
                  this.explode = function () { removeFromStack(this); };
                  stack.push(this);
                  makeObj.count += 1;
              }
          
              makeObj.checkInstances = function () { return stack.length; };
              makeObj.count = 0;
              return makeObj;
          
          }());
          
          
          // usage:
          
          var a = new Obj("Dave"),
              b = new Obj("Bob"),
              c = new Obj("Doug");
          
          Obj.count; // 3
          
          // "Dave? Dave's not here, man..."
          a.explode();
          
          Obj.count; // 2
          a = null;  // not 100% necessary, if you're never going to call 'a', ever again
                     // but you MUST call explode if you ever want it to leave the page's memory
                     // the horrors of memory-management, all over again
          

          这个模式会做你想做的事吗? 只要:

          1. 你不会把 a 变成别的东西
          2. 不要覆盖它的 explode 方法
          3. 你不要以任何方式惹Obj
          4. 您不希望任何prototype 方法可以访问任何内部变量

          ...那么是的,这种方法可以很好地让计数器正常工作。 您甚至可以编写一个名为recycle 的通用方法,它调用您传递给它的任何对象的explode 方法(只要它的构造函数或工厂支持这样的事情)。

          function recycle (obj) {
              var key;
              obj.explode();
              for (key in obj) { if (obj.hasOwnProperty(key)) { delete obj[key]; } }
              if (obj.__proto__) { obj.__proto__ = null; }
          }
          

          注意 - 这实际上不会摆脱对象。 您只需将其从闭包中移除,并移除它曾经拥有的所有方法/属性。

          所以现在它是一个空壳,你可以重复使用它,在回收它的部分后明确设置为null,或者让它被收集并忘记它,知道你删除了必要的引用。

          这有用吗? 应该不会吧。

          我真正认为这是有用的唯一一次是在游戏中,您的角色可能一次只能发射 3 发子弹,并且在屏幕上的第 1 发击中某人之前他不能射击第 4 发或者越界(这就是,比如说,魂斗罗在白天的工作方式)。

          您也可以将“消失”的子弹从堆栈中移出,然后通过重置其轨迹、重置适当的标志并将其推回堆栈来为任何玩家/敌人重复使用该子弹。

          但是,在代理允许我们定义“神奇”的构造函数/析构函数方法之前,这些方法在低级别得到尊重,这仅在您要对所有自己的对象的创建和销毁进行微观管理时才有用(真的不是一个好主意)。

          【讨论】:

          • 为什么要保留堆栈?你可以简单地减少计数器
          【解决方案8】:

          我的解决方案是创建一个对象存储实例计数和一个在原型中增加它们的函数。

          function Person() {
            this.countInst();
          }
          
          Person.prototype = {
            constructor: Person,
            static: {
              count: 0
            },
            countInst: function() {
              this.static.count += 1;
            }
          };
          
          var i;
          
          for (i = 0; i < 10; i++) {
            var p = new Person();
            document.write('Instance count: ');
            document.write(p.static.count);
            document.write('<br />');
          }
          

          这是我的小伙伴:https://plnkr.co/edit/hPtIR2MQnV08L9o1oyY9?p=preview

          【讨论】:

            【解决方案9】:
                    class Patient{
                    
                    constructor(name,age,id){        
                        Object.assign(this,{name, age, id});    
                        
                    }
                    static patientList = []; // declare a static variable
                    static addPatient(obj){       
                        this.patientList.push(...obj); // push to array
                        return this.patientList.length; // find the array length to get the number of objects
                    }
                    
                }
                
                let p1 = new Patient('shreyas',20, 1);
                let p2 = new Patient('jack',25, 2);
                let p3 = new Patient('smith',22, 3);
                let patientCount = Patient.addPatient([p1,p2,p3]); // call static method to update the count value with the newly created object
               console.log(Patient.patientList);
                console.log(patientCount);
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2018-01-22
              • 2016-01-19
              • 1970-01-01
              • 1970-01-01
              • 2023-01-25
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多