Javascript 中的对象类型

Objects types in Javascript

这是一个相当笼统的问题,来自处于学习阶段的新手,是我需要澄清的问题。

我目前正在学习对象,此时我正在学习继承。在本课的这一点上,我已经学习了几种不同的创建对象的方法,但是使用 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";

因为我似乎可以用前者做一些我不能用后者做的事情,我想知道我是否有任何理由不一直使用前一种方法。谁能帮我解决这个问题?我不需要很长的详细答案(尽管我会很感激)。这只是一个我想从脑海中抛开的问题,这样我就不会再纠结了。

前言:如果您不确定下面的 "prototype" 这个词是什么意思,请跳到分隔线下方进行解释,然后返回这里是答案的顶部。 :-)


假设在您的第一个示例中,您随后通过 new:

调用 Person
var person = new Person();

...那么 person 与您使用第二个示例得到的唯一区别与继承有关:通过 new Person 创建的对象在 Person.prototype作为其底层原型。

I'm wondering if there's any reason why I wouldn't just use the former method all the time

如果您不需要使用原型,那么使用构造函数可能会不必要地复杂化。请注意,您的第二种形式可以更简洁地写成:

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 非常灵活。 :-)


"Prototype"

JavaScript 使用 原型继承 ,这是一种奇特的说法,对象 A 可以被对象 B "backed",所以如果你问A 对于它没有的 属性,JavaScript 引擎将查看 B 上是否存在 属性。一个快速的实际示例:

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

不用担心 Object.create,现在您只需要知道它会创建一个新对象并根据您传递给它的对象分配其底层原型。所以 obj 得到了 proto 的支持。

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 这一事实感到困惑,并认为它是函数的原型。它不是。它只是函数对象上的普通 属性(函数是对象,可以有属性)。 only 的特殊之处在于,当您通过 new 调用函数时,new 会使用它。非函数对象没有 prototype 属性,它们的原型不是正常的 属性,它是内部的东西。您可以通过将对象传递给 Object.getPrototypeOf:

来获取对象的原型
var proto = {/*...*/};
var obj = Object.create(proto);
Object.getPrototypeOf(obj) === proto; // true