1

我最近一直在研究 Javascript,并认为我已经弄清楚了原型功能,直到遇到了一个小问题。

我发现如果您创建一个对象的两个新实例,并且在这两个对象中的任何一个中更改变量的值,它也会在另一个对象中更改,这不是我当时正在寻找的效果,不得不放入构造函数中的属性,而不是在每个实例中将它们分开。

什么时候需要让每个对象实例共享属性(字符串、布尔值等)?

原型真的只用于声明函数吗?你在构造函数中声明所有属性?

一个例子:

function Animal(name) {
    this.name = name;
}

Animal.prototype = {
    color: null,
    walk: function() {}
};

var dog = new Animal('Sam');
var horse = new Animal('Bob');

dog.color = 'black';
horse.color = 'chestnut';

“狗”对象的颜色现在是“栗子”,因为该属性在原型中并在两个实例之间共享。哪里需要这种效果?乍一看,我能从中看到的只是调试头痛,或者在 Javascript 中有很好的用途吗?有点让我想起了糟糕的全球变化。

如果大多数时候您不想在一个实例中更改属性并且它在所有其他实例中也发生更改,您是否只使用原型来声明函数?

4

2 回答 2

4

恕我直言,您误解了原型的行为方式。
解释一切将是一个太长的故事,但是关于原型上设置的属性,如果更改实例上的属性会更改所有实例的该值,那确实是一个无限的错误来源。
在您的马/狗示例上使用 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
于 2013-09-06T23:59:01.533 回答
3

您不应该在原型中分配变量(实际上您正在Animal.prototype使用自定义对象覆盖)。要定义实例属性,请在构造函数中定义它们,如下所示:

function Animal(name) {
    this.name = name;
    this.color = null;
}

// Instance method
Animal.prototype.walk = function() {};

// Static method
Animal.run = function() {};


// Inheritance example
function Dog(name) {
    // Call parent constructor
    Animal.apply(this, arguments);
}

Dog.prototype = Object.create(Animal.prototype);

Dog.prototype.walk = function() {
    // Call parent method (if you need)
    Animal.prototype.walk.apply(this, arguments);
    ...
};
于 2013-09-06T22:27:35.500 回答