前言:如果您不确定我下面的“原型”一词是什么意思,请跳到分隔线下方进行解释,然后回到答案顶部. :-)
假设在您的第一个示例中,您随后通过 new 调用 Person:
var person = new Person();
...那么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 使用 原型继承,这是一种奇特的说法,即对象 A 可以由对象 B “支持”,因此,如果您向 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这一事实感到困惑,并认为它是函数的原型。它不是。它只是函数对象的普通属性(函数是对象并且可以具有属性)。 唯一它的特殊之处在于new 在您通过new 调用函数时使用它。非函数对象没有prototype 属性,它们的原型不是普通属性,它是内部的东西。您可以通过将对象的原型传递给Object.getPrototypeOf:
var proto = {/*...*/};
var obj = Object.create(proto);
Object.getPrototypeOf(obj) === proto; // true