如何计算一个对象的实例数量?

13
如果我有一个定义为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时递增一个全局变量,这将是跟踪此计数器的唯一方法,但是否有比这个想法更好的方法呢?

编辑:

请看这里的建议:

enter image description here

我是如何将其恢复为2而不是3?

1
当一个对象超出其作用域时会发生什么? - Lee Taylor
2
不要添加全局变量作为计数器,而应该将属性添加到 MyObj.prototypeMyObj 中,或通过闭包添加私有变量(这些都无法解决 Lee Taylor 提到的问题)。 - nnnnnn
9个回答

20

虽然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
@nnnnnn:没错,这种简单的策略并没有跟踪对象实例的生命周期,因此即使对象符合垃圾回收条件,计数器也不会减少。(请注意,我的示例表明,即使无法访问两个匿名实例,它们仍然被计算在内。) - maerics
如果您添加一个小注释,说明这是创建的实例数量,那么就不会有人试图找出任何缺点了;-) - zerkms
太棒了,这正是我需要做的事情,实际上我不想要计数在对象外面,而是在对象本身中,非常感谢!!! - Johnydep
哦,实际上我遇到了与@nnnnnn建议的相同的问题,那么有没有解决办法,请再次检查我的问题。 - Johnydep

4
MyObj构造函数上创建一个静态属性,称为count,并在构造函数内部对其进行递增。
function MyObj() {
    MyObj.count++;
}

MyObj.count = 0;

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

alert(MyObj.count);

这是使用静态属性在Java中通常的方法。


1
我不确定这是否是非常可靠的方法。你假设MyObj可以从全局空间访问。但这并不总是成立。 - webduvet
我不明白你的意思。作用域与这个解决方案有什么关系? - Aadit M Shah

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}

3

使用ES6类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

提示:如果您不想将内部的currentId属性暴露在日志中,请将其设置为private

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


1
什么样的方法?
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();           

1
保持一个全局计数变量并每次增加也是一种选择。另一种选择是手动调用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变量:

在此输入图片描述

0
最终,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 发子弹,而且直到屏幕上的第一颗子弹击中某人或离开边缘后才能发射第四颗(就像以前的 Contra 游戏)。
你还可以将一个 "消失" 的子弹从堆栈中移除,并通过重置其轨迹、重置适当的标志,并将其推回到堆栈中以供任何玩家/敌人重用。
但再说一次,直到代理允许我们定义在低级别受到尊重的 "魔术" 构造函数/析构函数方法,这仅在你要微观管理自己所有对象的创建和销毁时才有用(真的不是一个好主意)。

为什么要保留栈?你可以简单地减少计数器。 - Rune FS

0
我的解决方案是创建一个对象存储实例计数和一个在原型中增加它们的函数。
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 />');
}

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


0
        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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接