在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(); 

实际产出

 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数组是在实例之间“共享”的。 如何防止这样的?

在这里演示

该线

 Person.prototype.friends = []; 

将“朋友”属性添加到“人员”原型中,使其与所有使用“人员”构造函数创build的新对象共享。 所以,如果你想让每个对象拥有自己的朋友,你必须将friends属性添加到单个对象中。

你真正想要做的就是你所做的名字:

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

在Javascript中,原型有点像其他OO语言中的基类(我稍微说了一些重要的原因,我将在稍后解释)。 当你把一些东西添加到原型中时,所有具有原型的东西都会被共享。 这就是为什么你的'sayHello'函数被添加到你的原型,因为你想让你的所有实例能够说你好。 通过添加朋友到原型,你说'我想要这个人types的所有事情共享。

关键是在Javascript中实际上有两个步骤来使对象看起来像一个类的成员。 第1步,创build一个原型,并添加将被共享的东西,通常的方法。 第2步,创build一个单独的对象后,将该属性添加到该对象。 如果你在步骤1中添加了你想要的“实例variables”,那么实际上你要做的就是创build共享的variables,就像你的方法一样,这就是你上面所做的。

我之前说过,原型有点像基类。 我有些说,因为它只是表面上看起来这样。 这是一个非常重要的细节和理解,它如何真正起作用,以后可以节省您的头痛和混乱。

了解Javascript的一个重要的事情是它没有类。 所以,与其他语言不同的是,其中有一类称为“类”,另一类称为“实例”,而Javascript只有对象。 即使看起来有一样东西是一个阶级,而另一个是这个阶级的一个实例,那也只是表象。 如果你不注意,那个外表可以欺骗你。

Javascript使用了一种叫做“原型inheritance”的东西,这是说对象从其他对象inheritance的很长一段路要走。 把它想象成一个链条。 如果你有提利昂,你可以访问说你好,就像这样:

 tyrion.sayHello() 

Javascript在tyrion对象中查找名为sayHello的属性。 它没有find它,所以它然后查找提利昂的原型,如果有的话,看看它是否有一个名为sayHello的属性。 这一次它发现它,确定它是一个函数,并调用它,告诉它,提示是应该在这个函数内的“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); } 

这是一个非常重要的细节,因为每个原型只是一个对象,可以随时修改 。 这意味着即使在创build了许多其他对象作为原型之后,也可以修改原型对象,而当您这样做时,基本上将事物添加到在其层次结构中使用该原型的每个对象。 对于来自语言的人来说,这是一个非常意外的行为,不允许您在创build“类”后对其进行更改。

在你的情况,上面,你正在修改原型的朋友列表,因为没有一个孩子有自己的“朋友”属性,当JavaScript去find“朋友”它总是find原型上的。

希望有所帮助,并为您节省一些头痛的道路。 如果你想了解更多的内容,Douglas Crockford的“Javascript:The Good Parts”是一本很好的书。

删除线

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

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

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

目前所有的原型都共享同一个对象的friends

你用于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 。 这是一个“类”方法,不会与您的实例方法冲突。 你不需要一个单独的文件。

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 

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

  Person.prototype.friends = []; 

意味着原型人拥有一个朋友数组,通过调用Person创build的所有实例共享为一个构造函数。

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

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

一般来说 – 原型是关于在JavaScript中共享function和属性。