恕我直言,您误解了原型的行为方式。
解释一切将是一个太长的故事,但是关于原型上设置的属性,如果更改实例上的属性会更改所有实例的该值,那确实是一个无限的错误来源。
在您的马/狗示例上使用 jsfiddle、jsbin、cssdeck 或任何您喜欢的测试站点看一下:当您更改马的颜色时,狗的颜色不会改变。它仍然是黑色的。
为什么呢 ?
当 dog 的实例对属性进行 READ 访问时,它将在原型上查找,然后在原型的原型上查找,依此类推,直到到达原型链的末端。在找到第一个值时,它将被返回,如果在原型上没有找到任何属性,则返回 undefined。
因此,在实例创建后,所有动物颜色都将为空。
现在,如果您对实例的属性进行 WRITE 访问,则会在此实例上创建一个全新的属性,并为其分配提供的值。这样原型保留了它的引用函数,而实例获得了它的值。宽慰。
在原型上定义属性仍然很有用,因为:
1)您确保所有实例将为这些属性提供有效值 - 默认值 -。
2)您可以通过分配一次在所有实例(静态)之间共享的属性(更重要的是:一个大对象)来节省内存。Expl : 狗的默认图像。
3) 在某些情况下,您可能希望一次更改所有实例的值。在这种情况下,更改原型上的值就可以做到这一点。
4)您允许 javascript 解释器通过将类(在 C++ 经典含义中)关联到您的 javascript 类来优化您的代码:当您分配原型上存在的属性时,该类不会“损坏”,并且 js 解释器可以继续使用背景类来表示实例。如果性能很重要,这是非常重要的一点。如果没有,那就忘了它:-)
所以只是一个小例子:如果你定义一个动物,那么每个实例都有它的颜色和名称:将它们设置在构造函数中而不是原型上是有意义的。但是以腿部计数为例,它不会从一个实例更改为另一个实例,并且如果您创建子类,您始终可以更改子原型上的值以让子类更改此计数:
function Animal(name, color) {
this.name = name;
this.color = null;
}
Animal.prototype.legCount = 4; // most animal have 4 legs (...)
// Dog Class, inheriting from Animal
function Dog(name) { Animal.apply(this, arguments); }
// Set Animal as Dog's prototype's prototype.
// so that we can safely change Dog's prototype.
Dog.prototype = Object.create(Animal.prototype);
// Duck class
function Duck(name) { Animal.apply(this, arguments); }
// same inheritance scheme
Duck.prototype = Object.create(Animal.prototype);
// ... but we change the legCount, for the ducks only
Duck.prototype.legCount = 2;
高级答案:
如果您希望原型属性保持不变,即使我们尝试为实例上的该属性分配值,请将其设置为原型上的只读属性。
Object.defineProperty(Duck.prototype,'legCount', { get : function() { return 2 } } );
var duck = new Duck(...) ;
duck.legCount = 5;
console.log (duck.legCount) ; // --> output is still 2
如果您希望在实例上设置值会更改所有实例的值(无需显式更改原型的属性值),请在原型属性定义的设置器中完成。
// using a closure
var duckLegCount = 2;
Object.defineProperty(Duck.prototype,'legCount', {
get : function() { return duckLegCount } ,
set : function (x) { duckLegCount = x } } );
var duck1 = new Duck(...);
var duck2 = new Duck(...);
duck1.legCount = 12;
console.log ( duck2.legCount ) ; // output is 12