我不会从问题的答案开始,而是从原型和构造函数的工作方式开始,以避免在尝试用部分理解来解释这些答案时造成混淆。所以,原型回顾:
-
JavaScript 中的每个值,除了 null 和 undefined,都有一个关联的值:它的原型。¹
-
原型用于查找属性。当您评估x.foo 时,您会检查值x 是否有一个自己的属性——它本身的一个属性——名为“foo”。如果是,x.foo 就是那个自己的属性的值。如果没有,则继续查找x 的原型。
-
值的原型可以是null,这意味着任何未找到自己的属性的属性查找都会导致undefined。
-
你可以通过函数Object.getPrototypeOf获取一个值的原型。
-
您可以使用函数Object.create 创建具有特定原型的新对象。
构造函数在 JavaScript 中有一个名为“prototype”的属性。这个属性的值不是构造函数的原型;它是使用构造函数创建的值的原型。以您的示例构造函数为例:
function Food() {}
如果您运行new Food(),将创建一个新对象并将其原型设置为Food.prototype,并且Food 将在this 设置为该新对象的情况下执行。换句话说,这是:
// create a new instance of Food
let f = new Food();
和这个意思一样:
// create a new object with Food.prototype as its prototype
let f = Object.create(Food.prototype);
// initialize it using the constructor
Food.call(f);
现在,上面总结的属性查找的工作方式产生了原型链。如果x 有原型y 而y 没有原型,则x.foo 会在这条链上查找:
x -> y -> null
- 如果
x 有自己的属性“foo”,x.foo 的计算结果就是它的值
- 如果
y 有一个自己的属性“foo”,x.foo 的计算结果就是它的值
- 我们已经到达链的末端
null,所以x.foo 是undefined
构造函数的prototype属性的默认值是一个新的Object实例,所以new Food()的原型链是这样的:
f -> Food.prototype -> Object.prototype -> null
如果x 的原型是C.prototype 或x 的原型不为空且C 的实例,您可以说值x 是构造函数C 的实例. (如果x 是 C.prototype,x 不是C 的实例。)这就是instanceof 运算符的工作原理²:
console.log({} instanceof Object); // true
console.log(Object.prototype instanceof Object); // false
如果C.prototype 是D 的实例,您也可以说C 继承自D。
JavaScript 内建的所有东西在其原型链上都有Object.prototype。函数是Object 实例:
function f() {}
f instanceof Object // true
构造函数也是如此:
function Food() {}
Food instanceof Object // true
需要注意的是,这并没有说明Food 和Object 实例之间的关系。你可以设置Food.prototype = null得到new Food() instanceof Object === false,但是Food instanceof Object还是会这样。
我希望这个框架足以解决您的问题。反正就是这么想的。仍然会使用它明确地回应他们:
手头的问题
假设我们创建了以下构造函数 Food。此时,函数 Food() 具有 Food.prototype 属性。由于 Food 是 Object 的一个实例,那么这意味着 Obect.prototype 是所有使用 Food() 创建的对象的原型属性。
使用new Food() 创建的所有对象都有Food.prototype 的原型。 Food.prototype的原型是Object.prototype。 Food是一个函数,也就是说它确实是Object的一个实例,但是这里Object的相关实例是Food.prototype。
然后我们通过将 Pizza 的原型属性设置为 Food 的实例来使 Pizza 继承自 Food。 Pizza 的原型属性现在是 Food.prototype,因为 Food 是 Pizza 的父级。
函数Pizza 的原型属性现在是一个以Food.prototype 为原型的对象。
myPizza 是否也有从 Pizza 继承的原型属性?
myPizza 不继承 Pizza 的任何内容。它继承了对象Pizza.prototype 的所有内容。由于Pizza.prototype 没有名为“prototype”的属性,因此myPizza 不会继承名为“prototype”的属性。
什么是 Object.prototype?它是 Object() 的属性吗?
Object.prototype 的字面意思是“Object 的‘原型’属性”,所以是的。这个值在 Object 的所有实例的原型链上,这很重要,因为 JavaScript 中的大多数东西都是 Object 的实例。除此之外,它就像构造函数的任何其他 prototype 属性。
只有函数有原型属性吗?
使用function 或class 关键字定义的函数以名为prototype 的属性开头。 (箭头函数不能,也不能用作构造函数。)您可以将名为 prototype 的属性放在任何东西上。它只有在构造函数上才有意义——与new 或instanceof 一起使用的函数。
Object.prototype 是对象吗?
当使用“object”来指代任何非原始²值时,它是一个带有小写“o”的对象。它不是Object 的实例——它有一个null 原型。
这个函数本身是一个对象吗?
是的,函数就是对象³。
Pizza.prototype 是否引用了创建 Pizza 构造函数的整个函数?
没有。 Pizza.prototype 不是函数。它由 Pizza 构造函数使用,但 Pizza 构造函数不是它的实例,也不是由它创建的。
还是 Pizza.prototype 只是指 Pizza() 范围内的内容?
也与范围无关。当您评估new Pizza() 时,Pizza 被调用,并以Pizza 的新实例作为其this 值。 this 不是函数的范围。 “范围”是可以看到某些变量集的区域。
function Foo() {
let x = 5; // a variable in scope. unrelated to `this`.
}
function Foo() {
this.x = 5; // assigning to a property of the value `this`.
// unrelated to variables.
}
Pizza.toppings 是 Pizza.prototype 的属性吗?
不是Pizza.toppings。有一个新对象——this——你将一个参数的值分配给Pizza函数,名为toppings,分配给该新对象的一个属性,也称为toppings。新对象的原型是Pizza.prototype,但新对象本身不是Pizza.prototype,所以答案是“不”,toppings 不是Pizza.prototype 的属性。
但是 Pizza.prototype 不是 Pizza() 的一个属性吗?
这是Pizza 的属性。 (只需确保Pizza() 指的是函数,而不是您通过调用函数获得的值。一定要准确!)
toppings 只是使用 Pizza 构造函数创建的对象的属性吗?
没错!
而 Pizza.prototype 是 Pizza 构造函数的属性?
是的。
目前的原型链如下:
myPizza --> Pizza.prototype --> Food.prototype --> Object.prototype
也正确。可以通过上述getPrototypeOf确认。
Object.prototype.toString = function () { return 'Object.prototype'; };
function Food() {}
Food.prototype.toString = function () { return 'Food.prototype'; };
function Pizza(toppings) {
this.toppings = toppings;
}
Pizza.prototype = Object.create(Food.prototype);
Pizza.prototype.toString = function () { return 'Pizza.prototype'; };
let myPizza = new Pizza();
myPizza.toString = function () { return 'myPizza'; };
let chainLink = myPizza;
while (true) {
console.log(String(chainLink));
if (chainLink === null) {
break;
}
chainLink = Object.getPrototypeOf(chainLink);
}
请注意,我在这里写的是 Object.create(Food.prototype) 而不是 new Food()。您不想在子构造函数之外运行父构造函数,尽管这在 ES3 中很常见。 ES5 添加了Object.create。 ES6 添加了class 和extends,这就是你要在实践中使用的。
¹原语在规范中没有 [[Prototype]] 但这并不重要,因为它们的属性查找和后 ES5 Object.getPrototypeOf 的工作方式与它们一样。
² 原语是字符串、布尔值、数字、符号、null 和 undefined。基元是不可变的——它们没有任何自己的属性。对象和基元之间的区别在 JavaScript 中并不那么重要,但是因为它们没有任何自己的属性,所以将它们用作原型是没有意义的。它们也不算是instanceof 任何东西,因为我将声称是历史原因。
³ 默认情况下,就是这样。您可以要求其中一位或全部不要与Object.setPrototypeOf 在一起。没有理由这样做。