简体   繁体   English

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

[英]Shared array between class instances in node.js

I have aa weird problem in node.js: 我在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();

Actual output 实际输出

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

Expected output 预期产出

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

How come that adding element to one instance does add it for both? 为什么在一个实例中添加元素会为两者添加它? It looks like the friend array is "shared" between instances. 看起来friend数组在实例之间“共享”。 How to prevent such? 如何预防?

Demo here 在这里演示

Remove lines 删除行

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

add them to constructor instead: 将它们添加到构造函数中:

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

At the moment all prototypes share same object friends . 目前,所有原型共享相同的对象friends

This pattern you're using for Person seems really strange to me. 你用于Person这种模式对我来说似乎很奇怪。 You don't have to wrap everything in anonymous functions in Node.js. 您不必将所有内容都包装在Node.js中的匿名函数中。

Have a look at this 看看这个

person.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;

Notice I grouped the factory within person.js as Person.create . 注意我将person.js中的工厂分组为Person.create This is a "class" method that won't conflict with your instance methods. 这是一个“类”方法,不会与您的实例方法冲突。 You don't need a separate file for it. 您不需要单独的文件。

index.js 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

The line 这条线

Person.prototype.friends = [];

Adds the friends property to the Person prototype, which makes it shared to all the new objects created with the Person constructor. 将friends属性添加到Person原型,这使得它与使用Person构造函数创建的所有新对象共享。 So, if you want each object to have its own friends, you have to add the friends property to the individual object. 因此,如果您希望每个对象都有自己的朋友,则必须将friends属性添加到单个对象。

What you actually want to do is exactly what you did with name: 你真正想做的就是你用名字做的事情:

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

In Javascript, a prototype is somewhat like a base class in other OO languages (I say somewhat for important reasons which I will explain in a moment). 在Javascript中,原型有点像其他OO语言的基类(我说有些重要的原因,我稍后会解释)。 When you add something to a prototype, it is shared by all the things that have that prototype. 当您向原型添加内容时,它将由具有该原型的所有内容共享。 This is why your 'sayHello' function is added to your prototype, because you want all your instances of Person to be able to sayHello. 这就是为什么你的'sayHello'函数被添加到原型中的原因,因为你希望你的所有Person实例能够说你好。 By adding friends to the prototype, you are saying 'I want this to be shared by all of the things of the Person type.' 通过向原型中添加朋友,您说'我希望所有这些都由Person类型共享。'

The point is that in Javascript there are actually two steps to making an object that looks like a member of a class. 关键是在Javascript中实际上有两个步骤可以使对象看起来像一个类的成员。 Step 1, create a prototype and add things that will be shared, usually the methods. 第1步,创建一个原型并添加将要共享的东西,通常是方法。 Step 2, after you create an individual object, add the properties to that object. 步骤2,在创建单个对象后,将属性添加到该对象。 If you add what you want to be 'instance variables' in Step 1, what you will actually do is create variables that are shared, just like your methods, which is what you did above. 如果您在步骤1中添加了您想要的“实例变量”,那么您实际要做的就是创建共享的变量,就像您的方法一样,这就是您在上面所做的。

I said before that a prototype is somewhat like a base class. 我之前说过,原型有点像基类。 I say somewhat because it only appears that way on the surface. 我有点说,因为它只是表面上的那种方式。 This is an extremely important detail and understanding how it really works will save you LOTS of headaches and confusion later. 这是一个非常重要的细节,了解它如何真正起作用将为您节省大量的麻烦和后来的困惑。

An important thing to understand about Javascript is that it does not have classes. 要了解关于JavaScript的一个重要的事情是,它没有类。 So unlike other languages where there is type of thing called a 'class', and another thing called an 'instance', Javascript only has objects. 因此,与其他语言不同的是,有一种称为“类”的东西,另一种称为“实例”,Javascript只有对象。 Even if it appears that one thing is a class, and another is an instance of that class, it is only appearance. 即使看起来一件事是一个类,另一件事是该类的一个实例,它只是外观。 If you are not paying attention, that appearance can fool you. 如果你没有注意,那个外表可以欺骗你。

Javascript uses something called 'prototypical inheritence,' which is a long way of saying that objects inherit from other objects. Javascript使用了一种称为“原型继承”的东西,这是一种很长的说法,即对象继承自其他对象。 Think of it like a chain. 把它想象成一个链条。 If you have tyrion and you access sayHello, like so: 如果你有tyrion并且你访问sayHello,就像这样:

tyrion.sayHello()

Javascript looks at the tyrion object for a property called sayHello. Javascript查看名为sayHello的属性的tyrion对象。 It does not find it, so it then looks up tyrion's prototype and if there is one, it looks at that to see if it has a property called sayHello. 它没有找到它,所以它然后查找了tyrion的原型,如果有的话,它会查看它是否有一个名为sayHello的属性。 This time it finds it, determines that it is a function, and calls it, telling it that tyrion should be 'this' within the function. 这次它找到它,确定它是一个函数,然后调用它,告诉它在函数中tyrion应该是'this'。 If it was written in javascript, it would look something like 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);
}

This is a very important detail to understand, because each prototype is just an object and can be modified at any time. 这是一个非常重要的细节,因为每个原型只是一个对象,可以随时修改 What that means is that you can modify the prototype object even after many other objects have been created using it as their prototype and when you do that you are essentially adding things to every object that uses that prototype somewhere in it's hierarchy. 这意味着即使在使用它作为原型创建了许多其他对象之后,您也可以修改原型对象,当您这样做时,您实际上是向在其层次结构中某处使用该原型的每个对象添加内容。 This is a very unexpected behavior for folks coming from languages that don't allow you to change the 'Class' after it's created. 对于来自语言的人来说,这是一个非常意外的行为,这些语言不允许您在创建后更改“类”。

In your case, above, you were modifying the prototype's friends list, and since none of the children had their own 'friends' property, when javascript went to find 'friends' it always found the one on the prototype. 在你的情况下,你正在修改原型的朋友列表,因为没有一个孩子有他们自己的'朋友'属性,当javascript去寻找'朋友'时,它总是找到原型上的那个。

Hope that helps, and saves you some headaches down the road. 希望有所帮助,并为您节省一些麻烦。 If you want to learn more about it, Douglas Crockford's 'Javascript: The Good Parts' is an excellent book to start with. 如果你想进一步了解它,Douglas Crockford的'Javascript:The Good Parts'是一本很好的书。

The prototype properties are shared between all objects who haave that prototype. 原型属性在拥有该原型的所有对象之间共享。

  Person.prototype.friends = []; 

Means the prototypical person has a friends array, shared across all instances created by calling Person as a constructor. 表示原型人有一个friends数组,在通过调用Person作为构造函数创建的所有实例之间共享。

Instead, you want to assign a new array to each person: 相反,您想为每个人分配一个新数组:

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

Generally speaking - the prototype is about sharing functionality and properties in JavaScript. 一般来说 - 原型是关于在JavaScript中共享功能和属性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM