node.js中类实例之间的共享数组

时间:2014-09-12 18:08:06

标签: javascript arrays node.js oop

我在node.js中遇到了一个奇怪的问题:

person.js

var Person;

Person = (function() {
  Person.prototype.name = "";
  Person.prototype.friends = [];

  function Person(name) {
    if (name) {
      this.name = name;
    }
  }

  Person.prototype.sayHello = function() {
    return console.log("Hello, my name is " + this.name + " and I have " + this.friends.length + " friends");
  };

  Person.prototype.addFriend = function(name) {
    this.friends.push(name);
  };

  return Person;

})();

module.exports = Person; 

factory.js

var Person = require('./Person.js');

module.exports = function(name) {
  return new Person(name);
};

index.js

factory = require('./factory');

tyrion = factory("Tyrion");
tyrion.addFriend("Bronn");
tyrion.sayHello();
daenerys = factory("Daenerys");
daenerys.addFriend("Illyrio");
daenerys.addFriend("Daario");
daenerys.addFriend("Barristan");
daenerys.sayHello();
tyrion.sayHello();

实际输出

Hello, my name is Tyrion and I have 1 friends
Hello, my name is Daenerys and I have 4 friends
Hello, my name is Tyrion and I have 4 friends

预期输出

Hello, my name is Tyrion and I have 1 friends
Hello, my name is Daenerys and I have 3 friends
Hello, my name is Tyrion and I have 1 friends

为什么在一个实例中添加元素会为两者添加它?看起来friend数组是"共享"实例之间。如何预防?

Demo here

4 个答案:

答案 0 :(得分:3)

删除行

Person.prototype.name = "";
Person.prototype.friends = [];

将它们添加到构造函数中:

this.name = name;
this.friends = [];

目前所有原型共享相同的对象friends

答案 1 :(得分:2)

Person使用的这种模式对我来说似乎很奇怪。您不必将所有内容都包装在Node.js中的匿名函数中。

看看这个

<强> person.js

function Person(name) {
  this.name = name || "";
  this.friends = [];
}

Person.prototype.sayHello = function sayHello() {
  console.log("Hello, my name is %s and I have %d friends", this.name, this.friends.length);
};

Person.prototype.addFriend = function addFriend(name) {
  this.friends.push(name);
};

// factory
Person.create = function create(name) {
  return new Person(name);
};

module.exports = Person;

注意我将person.js中的工厂分组为Person.create。这是一个&#34;类&#34;不会与您的实例方法冲突的方法。你不需要一个单独的文件。

<强> index.js

// don't forget your `var` keyword
var factory = require('./person').create;

tyrion = factory("Tyrion");
tyrion.addFriend("Bronn");
tyrion.sayHello();
// Hello, my name is Tyrion and I have 1 friends

daenerys = factory("Daenerys");
daenerys.addFriend("Illyrio");
daenerys.addFriend("Daario");
daenerys.addFriend("Barristan");
daenerys.sayHello();
// Hello, my name is Daenerys and I have 3 friends

tyrion.sayHello();
// Hello, my name is Tyrion and I have 1 friends

答案 2 :(得分:2)

该行

Person.prototype.friends = [];

将friends属性添加到Person原型,这使得它与使用Person构造函数创建的所有新对象共享。因此,如果您希望每个对象都有自己的朋友,则必须将friends属性添加到单个对象中。

你真正想要做的就是你用名字做的事情:

function Person(name) {
    // friends is a property of this, the new instance object.
    this.friends = [];
    if (name) {
        this.name = name;
    }
}

在Javascript中,原型有点像其他OO语言中的基类(我说有些重要原因,我稍后会解释)。当您向原型添加内容时,它将由具有该原型的所有内容共享。这就是为什么你的'sayHello'函数被添加到原型中的原因,因为你希望你的所有Person实例能够说你好。通过向原型中添加朋友,您说'我希望由Person类型的所有内容共享此内容。'

重点是,在Javascript中,实际上有两个步骤可以使对象看起来像一个类的成员。第1步,创建一个原型并添加将要共享的东西,通常是方法。步骤2,在创建单个对象后,将属性添加到该对象。如果你在步骤1中添加你想要成为'实例变量'的东西,那么你实际要做的就是创建共享的变量,就像你的方法一样,这就是你在上面所做的。

我之前说过,原型有点像基类。我有点说,因为它只是表面上的那种方式。这是一个非常重要的细节,了解它如何真正起作用将为您节省大量的麻烦和后来的困惑。

理解Javascript的一个重要事情是有类。因此,与其他语言不同的是,有一种称为“类”的东西,另一种称为“实例”,Javascript只有对象。即使看起来一件事是一个类,另一件事是该类的一个实例,它只是外观。如果你没有注意,这种表象可以欺骗你。

Javascript使用称为“原型继承”的东西,这是一种很长的说法,即对象从其他对象继承。把它想象成一个链条。如果你有tyrion并且你访问sayHello,就像这样:

tyrion.sayHello()

Javascript查看名为sayHello的属性的tyrion对象。它没有找到它,所以它然后查找了tyrion的原型,如果有的话,它会查看它是否有一个名为sayHello的属性。这次它找到它,确定它是一个函数,然后调用它,告诉它在函数中tyrion应该是'this'。如果它是用javascript编写的,它看起来像这样:

function find_property(original_obj, property_name) {

    var found_prop = undefined;
    var current_obj = original_obj;

    // we keep searching until we either have a property or we run out of 
    // places to look.
    while(found_prop == undefined && current_obj != undefined) {
       // does the object we are looking at have it's own property with that name?
       if ( obj.hasOwnProperty(property_name) ) {
           // yes, so we can set found_prop
           found_prop = obj[property_name];
       } else {
           // no, we have to look at the next prototype up the chain.
           current_obj = current_obj.__proto__;
       }
    }
    return found_prop;
}

var sayhello = find_property(tyrion, 'sayHello');
if (typeof sayhello == 'function') {
   sayhello.call(tyrion);
}

这是一个非常重要的细节,因为每个原型只是一个对象,可以随时修改。这意味着即使在使用它作为原型创建了许多其他对象之后,您也可以修改原型对象,当您这样做时,您实际上是向在其层次结构中某处使用该原型的每个对象添加内容。对于来自语言的人来说,这是一个非常意外的行为,这些语言不允许您在创建后更改“类”。

在你的情况下,在你的情况下,你正在修改原型的朋友列表,因为没有一个孩子有他们自己的'朋友'属性,当javascript去找'朋友'时,它总是找到原型上的那个。

希望有所帮助,并为您节省一些麻烦。如果你想进一步了解它,Douglas Crockford的“Javascript:The Good Parts”是一本很好的书。

答案 3 :(得分:1)

原型属性在拥有该原型的所有对象之间共享。

  Person.prototype.friends = []; 

表示原型人有一个friends数组,通过调用Person作为构造函数创建的所有实例共享。

相反,您想为每个人分配一个新数组:

function Person(name) {
  if (name) {
    this.name = name;
  }
  this.friends = []; // create a new array in the constructor
}

一般来说 - 原型是关于在JavaScript中共享功能和属性。