JavaScript Prototype
本文最后更新于 2024年4月23日 早上
Introduction to JavaScript prototype
在 JavaScript 中,对象可以通过原型继承彼此的功能。每个对象都有自己的属性,称为 prototype
。
因为原型本身也是另一个对象,所以原型有自己的原型。这创建了一个称为原型链(prototype chain)的东西。当原型的自身原型为空时,原型链结束。
假设有一个对象 person,其属性名为 name:
1 |
|
当在控制台中检查 person 对象时,会发现 person 对象有一个名为 prototype 的属性,用 [[Prototype]] 表示:
原型本身是一个具有自己属性的对象:
当访问对象的属性时,如果该对象具有该属性,它将返回该属性值。
但是,如果访问对象中不存在的属性,JavaScript 引擎将在该对象的原型中搜索。
如果 JavaScript 引擎在对象的原型对象中找不到该属性,它将在原型的原型中搜索,直到找到该属性或到达原型链的末尾。
JavaScript prototype illustration
JavaScript 具有内置的 Object() 函数。如果将 Object 函数传递给 typeof 运算符,则它会返回“function”。
1 |
|
此外,JavaScript 提供了一个匿名对象,可以通过 Object() 函数的原型属性来引用:
1 |
|
Object.prototype 对象具有一些有用的属性和方法,例如 toString() 和 valueOf()。
Object.prototype 还有一个重要的属性:constructor,它引用 Object() 函数。
以下语句确认 Object.prototype.constructor 属性引用了 Object 函数:
1 |
|
首先,定义一个名为 Person 的构造函数,如下所示:
1 |
|
在此示例中,Person() 函数接受一个 name 参数并将其分配给 this 对象的 name 属性。
JavaScript 在幕后创建了一个新函数 Person() 和一个匿名对象:
与 Object() 函数一样,Person() 函数有一个名为 prototype 的属性,该属性引用匿名对象。匿名对象具有引用 Person() 函数的构造函数属性。
![img](D:\2024 年\blogs\Web Dev\JavaScript\ECMAScript\04-Objects\assets\JavaScript-Prototype-Person-function.png)
此外,JavaScript 通过 [[Prototype]] 将 Person.prototype 对象链接到 Object.prototype 对象,这称为原型链接。
原型链接在下图中用[[Prototype]]表示:
Defining methods in the JavaScript prototype object
下面在 Person.prototype 对象中定义了一个名为 greet()的新方法:
1 |
|
在这种情况下,JavaScript 引擎将 greet()方法添加到 Person.prototype 对象中:
以下创建了 Person 的一个新实例:
1 |
|
在内部,JavaScript 引擎创建一个名为 p1 的新对象,并通过原型链接将 p1 对象链接到 Person.prototype 对象:
p1、Person.prototype 和 Object.protoype 之间的链接称为原型链。
以下代码调用 p1 对象上的 greet()方法:
1 |
|
因为 p1 没有 greet()方法,所以 JavaScript 遵循原型链接并在 Person.prototype 对象上找到它。
由于 JavaScript 可以在 Person.prototype 对象上找到 greet() 方法,因此它执行 greet() 方法并返回结果:
下面调用 p1 对象的 toString() 方法:
1 |
|
在这种情况下,JavaScript 引擎会沿着原型链在 Person.prototype 中查找 toString() 方法。
由于 Person.prototype 没有 toString() 方法,因此 JavaScript 引擎会向上查找原型链并在 Object.prototype 对象中搜索 toString() 方法。
如果调用 Person.prototype 和 Object.prototype 对象上不存在的方法,JavaScript 引擎将遵循原型链,如果找不到该方法,则会抛出错误。例如:
1 |
|
由于 fly() 方法不存在于原型链中的任何对象上,因此 JavaScript 引擎会发出以下错误:
1 |
|
下面创建了 name 属性为“Jane”的 Person 的另一个实例:
1 |
|
p2 对象具有与 p1 对象相同的属性和方法。
总之,当你在原型对象上定义一个方法时,这个方法被所有实例共享。
Defining methods in an individual object
下面定义了 p2 对象上的 draw() 方法。
1 |
|
JavaScript 引擎将 draw() 方法添加到 p2 对象,而不是 Person.prototype 对象:
这意味着只可以在 p2 对象上调用 draw() 方法:
1 |
|
但是你不能在 p1 对象上调用 draw() 方法:
1 |
|
当在对象中定义方法时,该方法仅对该对象可用。默认情况下它不能与其他对象共享。
Getting prototype linkage
__proto__
发音为 dunder proto。 __proto__
是 Object.prototype 对象的访问器属性。它公开了访问它的对象的内部原型链接([[Prototype]])。
__proto__
已在 ES6 中标准化,以确保与 Web 浏览器的兼容性。但是,将来可能会弃用它,转而使用 Object.getPrototypeOf()。因此,永远不应该在生产代码中使用 __proto__
。
p1.__proto__
公开引用 Person.prototype 对象的 [[Prototype]]。
同样,p2.__proto__
也引用与 p1.__proto__
相同的对象:
1 |
|
如前所述,应该使用 Object.getPrototypeOf() 方法而不是 __proto__
。 Object.getPrototypeOf() 方法返回指定对象的原型。
1 |
|
获取原型链接的另一种流行方法是当 Object.getPrototypeOf() 方法无法通过构造函数属性使用时,如下所示:
1 |
|
p1.constructor 返回 Person,因此 p1.constructor.prototype 返回原型对象。
Shadowing
观察以下方法调用:
1 |
|
p1 对象没有定义 greet() 方法,因此 JavaScript 会沿着原型链查找它。在这种情况下,它可以在 Person.prototype 对象中找到该方法。
让我们向对象 p1 添加一个与 Person.prototype 对象中的方法同名的新方法:
1 |
|
并调用 greet() 方法:
1 |
|
因为 p1 对象有 greet() 方法,JavaScript 只是立即执行它,而无需在原型链中查找它。
这是 shadowing 的一个例子。 p1 对象的 greet() 方法隐藏了 p1 对象引用的原型对象的 greet() 方法。