我在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
数组是"共享"实例之间。如何预防?
答案 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中共享功能和属性。