JavaScript中的对象类型

5
这是一个由学习阶段的新手提出的相当普遍的问题,需要澄清一些事情。
我目前正在学习有关对象的内容,目前正在学习继承。在课程的这个阶段,我已经学会了几种不同的创建对象的方法,但是使用this关键字创建对象似乎具有最多的功能:
function Person(){
    this.eyes = 2;
    this.ears = 2;
    this.arms = 2;
    this.hands = 2;
    this.feet = 2;
    this.legs = 2;
    this.species = "Homo sapien";
    }

我明白我可以用这个做什么,但是有一个对象是用这种方式创建的:
var person = new Object();
person.eyes = 2;
person.ears = 2;
person.arms = 2;
person.hands = 2;
person.feet = 2;
person.legs = 2;
person.species = "Homo sapien";

因为我似乎可以用前者做一些后者无法做到的事情,所以我想知道是否有任何理由我不应该一直使用前者方法。有人能帮助我吗?我不需要长篇详细的答案(尽管这样的回答会受到欢迎)。这只是一个我想解决的问题,以便我不再纠结于此。

4
“因为我似乎能够用前者做一些后者做不到的事情。” 比如什么?脑海中只想到一个(确实很重要的)例子,就是将原型属性与实例关联起来。 - T.J. Crowder
2
请注意,Person 不是一个对象,而是一个构造函数。要获取一个对象,您必须使用 new 运算符调用该构造函数。 - Teemu
2
我绝不建议使用 new Object(),对于这个功能,请编写一个字面量{} - Paul S.
1个回答

11

前言: 如果你不确定下面所说的“原型”是什么意思,请跳到下面的解释部分,然后再回到这个答案的开头。 :-)


假设在你的第一个例子中,你通过new调用了Person:

var person = new Person();

如果你仅仅是想创建一个`person`对象,那么使用第二种方法和使用`new Person`创建的对象唯一不同的地方就在于继承:通过`new Person`创建的对象会将`Person.prototype`对象分配为其底层原型。

我想知道是否有任何理由我不能一直使用前者的方法

如果你不需要使用原型,那么使用构造函数可能会使事情变得更加复杂。请注意,你的第二种方式可以更简洁地编写:

var person = {
    eyes: 2,
    ears: 2,
    arms: 2,
    hands: 2,
    feet: 2,
    legs: 2,
    species: "Homo sapien"
};

这被称为对象初始化器:它创建一个新的对象,并列出你看到的属性。如果你想要一个新的,空的对象,不需要使用 x = new Object(); 只需使用 x = {};

当对象是一次性的时候,直接创建它通常是最简单的方法。

构造函数的主要优势在于它们是工厂函数,用于创建基本相似的对象:具有相同的初始属性,具有相同的底层原型等。该函数可以接受参数并将其用于适当地为其创建对象,可能会对构造参数进行一些验证等。也就是说:它们集中了初始化逻辑。

构造函数不是唯一的函数工厂方式。你也可以这样做:

function buildPerson() {
    return {
        eyes: 2,
        ears: 2,
        arms: 2,
        hands: 2,
        feet: 2,
        legs: 2,
        species: "Homo sapien"
    };
}
var person = buildPerson();

如果您希望该人拥有一个原型(适用于ES5浏览器及更高版本):

var personPrototype = {
    // ...properties for the person prototype...
};
function buildPerson() {
    var obj = Object.create(personPrototype);
    obj.eyes = 2;
    obj.ears = 2;
    obj.arms = 2;
    obj.hands = 2;
    obj.feet = 2;
    obj.legs = 2;
    obj.species = "Homo sapien";
    return obj;
}
var person = buildPerson();

(还有一种更冗长的定义这些属性的方式。)

JavaScript非常灵活。 :-)


“原型”

JavaScript使用原型继承,这是一种说法,即可以通过对象B“支持”对象A,因此,如果您要求A具有不存在的属性,则JavaScript引擎将查看该属性是否存在于B上。快速实用的例子:

var proto = {
    name: "proto's name"
};
var obj = Object.create(proto); // Creates an object backed by the given prototype

现在不要担心Object.create,你只需要知道它创建一个新的对象,并根据你传递给它的对象分配其基础原型。所以objproto支持。

obj没有name属性,但如果我们这样做:

console.log(obj.name);

...我们看到"proto's name"。这是因为当JavaScript引擎试图从obj获取name的值时,发现obj没有name属性,因此它查找obj的原型proto。在那里找到了它,并使用了proto中的值。

这仅在获取值时发生(除非在一些高级情况下,现在可以忽略)。在设置属性值时,它被设置在您设置它的对象上。所以:

var proto = {
    name: "proto's name"
};
var obj = Object.create(proto); // `obj` is backed by `proto`
console.log(obj.name);          // "proto's name"
obj.name = "obj's name";
console.log(obj.name);          // "obj's name"

原型的目的是重用,因此一个对象可以成为多个其他对象的原型,这并不奇怪:

var proto = {
    name: "proto's name"
};
var a = Object.create(proto);   // `a` is backed by `proto`
var b = Object.create(proto);   // `b` is also backed by `proto`
console.log(a.name);            // "proto's name"
console.log(b.name);            // "proto's name"
a.name = "a's name";
console.log(a.name);            // "a's name"
console.log(b.name);            // "proto's name"

原型对象是普通对象,我们可以更改它们:

var proto = {
    name: "proto's name"
};
var obj = Object.create(proto);
console.log(obj.name);          // "proto's name"
proto.name = "updated";
console.log(obj.name);          // "updated"

由于obj没有自己的name属性,每次访问它时,JavaScript引擎都会去查找它的原型。

new操作符自动为创建的对象分配一个原型:它使用函数的prototype属性上拥有的对象。因此:

function Person(name) {
    this.name = name;
}
Person.prototype.sayName = function() {
    console.log("My name is " + this.name);
};
var p = new Person("Fred"); // Creates an object backed by Person.prototype,
                            // then calls Person with this referring to the
                            // object
p.sayName();                // "My name is Fred";

最后:由于原型对象是正常的对象,因此它们也可以有原型:

var rootProto = {
    name: "root proto's name"
};
var middleProto = Object.create(rootProto);
middleProto.middleProp = "middle property";
var obj = Object.create(middleProto);
console.log(obj.name);       // "root proto's name"
console.log(obj.middleProp); // "middle property"
对于name,JavaScript引擎会查看obj,并没有找到name属性,所以它会查看middleProto。在那里也没有找到name属性,所以它会查看rootProto。它在那里找到了,所以使用它。
混淆点:很多人被构造函数上的属性称为prototype所困惑,并认为它是函数的原型。它不是。它只是函数对象上的普通属性(函数是对象,可以有属性)。唯一特殊的地方就是当您通过new调用函数时,其会被new使用。非函数对象没有prototype属性,它们的原型不是普通属性,而是内部属性。您可以通过将对象传递给Object.getPrototypeOf来获取对象的原型。
var proto = {/*...*/};
var obj = Object.create(proto);
Object.getPrototypeOf(obj) === proto; // true

这正是我在寻找的答案。顺便提一下,我想补充一点,这些例子都是从我正在阅读的文本中复制出来的,我自己也想知道为什么它在使用这种方法(x = new Object();)和这种方法(x = {};)之间来回切换。无论如何,你很好地解释了为什么我想要使用其中一种方法而不是另一种方法,这就是我在答案中寻找的内容。 - AGx-07_162
1
@AGx-07_162:你对此的好奇心是你在 JavaScript 未来发展中非常好的迹象。 :-) - T.J. Crowder

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