JavaScript - 在构造函数或构造函数的原型属性中设置属性?

7

我曾看到和听说过,应该将方法设置在构造函数的原型属性中,以便不会有多个不同实例。但是对于属性本身呢?哪种是最佳实践?如果这样的话,构造函数不应始终为空吗?

function Gadget(name, color) {
     this.name = name;
     this.color = color;
     this.whatAreYou = function(){
       return 'I am a ' + this.color + ' ' + this.name;
     }
}

这真的应该是...吗?

function Gadget(name,color){}

Gadget.prototype.name = name;
Gadget.prototype.color = color;
Gadget.prototype.whatAreYou = function() {
   return 'I am a ' + this.color + ' ' + this.name;
};
2个回答

8
如果您在原型上设置它,该属性将被所有实例共享。通常不是您想要的。我在http://js-bits.blogspot.com/2014/10/understanding-prototypical-inheritance.html上写过关于此的博客。通常,您希望每个小工具都有自己的名称和颜色。您如何使用您提出的代码来实现这一点?
顺便说一下,您提出的代码有未定义的变量(名称、颜色)
正常的方法是在原型上设置方法,在对象本身上设置常规值。除非您确实希望属性被所有实例共享,这就像我们在静态类型语言中所称的静态属性。
以下是一个例子
function Gadget(name,color){
    this.name = name;
    this.color = color;
    // Since all gadgets in on the prototype, this is shared by all instances;
    // It would more typically be attached to Gadget.allGadgets instead of the prototype
    this.allGadgets.push(this);
    // Note that the following would create a new array on the object itself
    // not the prototype
    // this.allGadgets = [];

}

Gadget.prototype.allGadgets = [];
Gadget.prototype.whatAreYou = function() {
    return 'I am a ' + this.color + ' ' + this.name;
};

一个重要的概念是,写操作(赋值)总是应用于对象本身,而不是原型。然而,读取操作会沿着原型链向上遍历,查找该属性。

也就是说

function Obj() {
   this.map = {};
}

function SharedObj() {}
SharedObj.prototype.map = {};

var obj1 = new Obj();
var obj2 = new Obj();
var shared1 = new SharedObj();
var shared2 = new SharedObj();

obj1.map.newProp = 5;
obj2.map.newProp = 10;
console.log(obj1.map.newProp, obj2.map.newProp); // 5, 10

// Here you're modifying the same map
shared1.map.newProp = 5;
shared2.map.newProp = 10;
console.log(shared1.map.newProp, shared2.map.newProp); // 10, 10

// Here you're creating a new map and because you've written to the object directly    
// You don't have access to the shared map on the prototype anymore
shared1.map = {};
shared2.map = {};
shared1.map.newProp = 5;
shared1.map.newProp = 10;
console.log(shared1.map.newProp, shared2.map.newProp); // 5, 10

1
注意:放在原型上的属性可以作为实例值的默认值。 - Alnitak
谢谢指出这个问题,那么可以说构造函数只应该为传入的值设置属性吗? - KingKongFrog
2
@Alnitak,没问题,只要它不是引用类型(对象、数组),因为它会被共享。 - Ruan Mendes

2

对于属性,它可能不会按照你的预期工作。方法应该在原型上声明,因为你希望所有实例共享相同的函数引用。属性通常是每个实例都不同的。当处理基元时,你可能注意不到这一点,但是对于对象(或数组等)的属性,您将经历“奇怪”的行为。

function Test(){}
Test.prototype.foo = {bar: 1};
Test.prototype.baz = 1;

var one = new Test();
var two = new Test();
one.foo.bar = 3;
two.foo.bar === 3; // true, because of object reference
// BUT:
one.baz = 3;
two.baz === 1; // true

1
奇怪的行为 -> 共享属性。当你理解发生了什么时,它就不那么奇怪了。 - Ruan Mendes
现在我有点困惑...哈哈。所以你改变了bar的值,因为它是一个对象引用,所以所有实例都被更新了。然而,属性没有改变,因为它们都有自己的属性?我尝试使用数组做同样的事情Test.prototype.bee = [1,2,3];,然后one.bee.push(8);,这也只更新/创建了'one'的bee属性...但不是'two'。所以这只适用于对象,而不是数组? - KingKongFrog
1
@KingKongFrog 如果你执行 one.bee = [],那么会在对象本身上创建一个新的数组,而不是原型,它不是共享的。如果你执行 one.bee.push(8),那么你只是修改了现有的数组。赋值操作符(=)总是在对象本身上进行,但读取是通过原型链完成的。 - Ruan Mendes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接