javascript - Shared array between class instances in node.js - Stack Overflow

I have a a weird problem in node.js:person.jsvar Person;Person = (function() {Person.prototype.name = &

I have a a weird problem in 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 e that adding element to one instance does add it for both? It looks like the friend array is "shared" between instances. How to prevent such?

Demo here

I have a a weird problem in 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 e that adding element to one instance does add it for both? It looks like the friend array is "shared" between instances. How to prevent such?

Demo here

Share Improve this question asked Sep 12, 2014 at 18:08 Vinz243Vinz243 9,95611 gold badges45 silver badges95 bronze badges
Add a ment  | 

4 Answers 4

Reset to default 3

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.

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. So, if you want each object to have its own friends, you have to add the friends property to the individual object.

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). 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. By adding friends to the prototype, you are saying 'I want this to be shared by all of the things of the Person type.'

The point is that in Javascript there are actually two steps to making an object that looks like a member of a class. Step 1, create a prototype and add things that will be shared, usually the methods. Step 2, after you create an individual object, add the properties to that object. 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.

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. So unlike other languages where there is type of thing called a 'class', and another thing called an 'instance', Javascript only has objects. 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. Think of it like a chain. If you have tyrion and you access sayHello, like so:

tyrion.sayHello()

Javascript looks at the tyrion object for a property called sayHello. 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. This time it finds it, determines that it is a function, and calls it, telling it that tyrion should be 'this' within the function. If it was written in javascript, it would look something like this:

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 ing 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.

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.

This pattern you're using for Person seems really strange to me. You don't have to wrap everything in anonymous functions in Node.js.

Have a look at this

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. This is a "class" method that won't conflict with your instance methods. You don't need a separate file for it.

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

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.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1742324440a4422463.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信