摘要:在本教程中,您将了解 JavaScript 原型以及它在幕后是如何工作的。
JavaScript 原型的介绍
在 JavaScript 中,对象可以通过原型继承彼此的功能。每个对象都有一个名为prototype的属性。
由于prototype本身也是另一个对象,因此prototype也有自己的prototype。这会创建一个称为原型链的东西。当一个原型的prototype属性为null时,原型链结束。
假设你有一个名为person的对象,它有一个名为name的属性
let person = {'name' : 'John'}Code language: JavaScript (javascript)在控制台中检查person对象时,您会发现person对象有一个名为prototype的属性,用[[Prototype]]表示

原型本身是一个对象,它有自己的属性

当您访问对象的属性时,如果对象具有该属性,它将返回属性值。以下示例访问person对象的name属性

它按预期返回name属性的值。
但是,如果您访问一个对象中不存在的属性,JavaScript 引擎将在对象的原型中搜索。
如果 JavaScript 引擎无法在对象的原型中找到该属性,它将在原型的原型中搜索,直到找到该属性或到达原型链的末尾。
例如,您可以像这样调用person对象的toString()方法

toString()方法返回person对象的字符串表示形式。默认情况下,它是[object Object],这并不明显。
请注意,当一个函数是对象属性的值时,它被称为方法。因此,方法是具有函数值为属性的属性。
在这个例子中,当我们在person对象上调用toString()方法时,JavaScript 引擎会在person对象中找到它。
由于person对象没有toString()方法,它将在person的原型对象中搜索toString()方法。
由于person的原型具有toString()方法,因此 JavaScript 调用了person原型对象的toString()。

JavaScript 原型说明
JavaScript 有内置的Object()函数。如果您将Object函数传递给typeof运算符,它将返回'function'。例如
typeof(Object)Code language: JavaScript (javascript)输出
'function'Code language: JavaScript (javascript)请注意,Object()是一个函数,而不是一个对象。如果您第一次了解 JavaScript 原型,这会令人困惑。
此外,JavaScript 提供了一个匿名对象,可以通过Object()函数的prototype属性引用该对象
console.log(Object.prototype);Code language: JavaScript (javascript)Object.prototype对象具有一些有用的属性和方法,例如toString()和valueOf()。
Object.prototype还有一个重要的属性,称为constructor,它引用Object()函数。
以下语句确认了Object.prototype.constructor属性引用了Object函数
console.log(Object.prototype.constructor === Object); // trueCode language: JavaScript (javascript)假设一个圆圈代表一个函数,一个正方形代表一个对象。下图说明了Object()函数和Object.prototype对象之间的关系
首先,定义一个名为Person的构造函数,如下所示
function Person(name) {
this.name = name;
}Code language: JavaScript (javascript)在这个例子中,Person()函数接受一个name参数,并将其分配给this对象的name属性。
在幕后,JavaScript 创建了一个新的函数Person()和一个匿名对象
与Object()函数一样,Person()函数也有一个名为prototype的属性,它引用一个匿名对象。匿名对象具有constructor属性,它引用Person()函数。
以下是Person()函数和Person.prototype引用的匿名对象
console.log(Person);
console.log(Person.prototype);Code language: CSS (css)此外,JavaScript 通过[[Prototype]]将Person.prototype对象链接到Object.prototype对象,这被称为原型链接。
下图中用[[Prototype]]表示原型链接
在 JavaScript 原型对象中定义方法
以下是在Person.prototype对象中定义一个名为greet()的新方法
Person.prototype.greet = function() {
return "Hi, I'm " + this.name + "!";
}Code language: JavaScript (javascript)在这种情况下,JavaScript 引擎将greet()方法添加到Person.prototype对象
以下创建了Person的新实例
let p1 = new Person('John');Code language: JavaScript (javascript)在内部,JavaScript 引擎创建一个名为p1的新对象,并通过原型链接将p1对象链接到Person.prototype对象
p1、Person.prototype和Object.protoype之间的链接被称为原型链。
以下在p1对象上调用greet()方法
let greeting = p1.greet();
console.log(greeting);Code language: JavaScript (javascript)由于p1没有greet()方法,JavaScript 遵循原型链接并在Person.prototype对象上找到它。
由于 JavaScript 可以在Person.prototype对象上找到greet()方法,因此它执行greet()方法并返回结果
以下在p1对象上调用toString()方法
let s = p1.toString();
console.log(s);Code language: JavaScript (javascript)在这种情况下,JavaScript 引擎遵循原型链在Person.prototype中查找toString()方法。
由于Person.prototype没有toString()方法,JavaScript 引擎向上到原型链,并在Object.prototype对象中搜索toString()方法。
由于 JavaScript 可以在Object.prototype中找到toString()方法,因此它执行toString()方法。
如果您调用Person.prototype和Object.prototype对象上不存在的方法,JavaScript 引擎将遵循原型链,如果找不到该方法,则会抛出错误。例如
p1.fly();
Code language: CSS (css)由于原型链上的任何对象都不存在fly()方法,因此 JavaScript 引擎会发出以下错误
TypeError: p1.fly is not a functionCode language: JavaScript (javascript)以下创建了另一个Person的实例,其name属性为'Jane'
let p2 = new Person('Jane');Code language: JavaScript (javascript)p2对象具有与p1对象相同的属性和方法。
总之,当您在prototype对象上定义一个方法时,该方法由所有实例共享。
在单个对象中定义方法
以下在p2对象上定义draw()方法。
p2.draw = function () {
return "I can draw.";
};
Code language: JavaScript (javascript)JavaScript 引擎将draw()方法添加到p2对象,而不是Person.prototype对象
这意味着您可以在p2对象上调用draw()方法
p2.draw();Code language: CSS (css)但您不能在p1对象上调用draw()方法
p1.draw()Code language: CSS (css)错误
TypeError: p1.draw is not a functionCode language: JavaScript (javascript)当您在对象中定义方法时,该方法仅对该对象可用。默认情况下,它不能与其他对象共享。
获取原型链接
__proto__发音为 dunder proto。__proto__是Object.prototype对象的访问器属性。它通过访问它来公开对象的内部原型链接([[Prototype]])。
__proto__已在ES6中标准化,以确保与 Web 浏览器的兼容性。但是,它可能会在将来被弃用,转而使用Object.getPrototypeOf()。因此,您不应在生产代码中使用__proto__。
p1.__proto__公开了引用Person.prototype对象的[[Prototype]]。
类似地,p2.__proto__也引用了与p1.__proto__相同的对象:
console.log(p1.__proto__ === Person.prototype); // true
console.log(p1.__proto__ === p2.__proto__); // trueCode language: JavaScript (javascript)如前所述,您应该使用Object.getPrototypeOf()方法而不是__proto__。Object.getPrototypeOf()方法返回指定对象的原型。
console.log(p1.__proto__ === Object.getPrototypeOf(p1)); // trueCode language: JavaScript (javascript)另一种获取原型链接的常用方法是通过constructor属性获得Object.getPrototypeOf()方法时,如下所示
p1.constructor.prototypeCode language: CSS (css)p1.constructor返回Person,因此,p1.constructor.prototype返回原型对象。
阴影
请看以下方法调用
console.log(p1.greet());Code language: CSS (css)p1对象没有定义greet()方法,因此 JavaScript 向上到原型链以找到它。在这种情况下,它可以在Person.prototype对象中找到该方法。
让我们在p1对象中添加一个新方法,其名称与Person.prototype对象中的方法相同
p1.greet = function() {
console.log('Hello');
}Code language: JavaScript (javascript)并调用greet()方法
console.log(p1.greet());Code language: CSS (css)由于p1对象具有greet()方法,因此 JavaScript 会立即执行它,而不会在原型链中查找它。
这是一个阴影的例子。p1对象的greet()方法会遮蔽p1对象引用的prototype对象的greet()方法。
总结
Object()函数有一个名为prototype的属性,它引用一个Object.prototype对象。Object.prototype对象具有所有在所有对象中可用的属性和方法,例如toString()和valueOf()。Object.prototype对象具有constructor属性,它引用Object函数。- 每个函数都有一个
prototype对象。这个原型对象通过[[prototype]]链接或__proto__属性引用Object.prototype对象。 - 原型链允许一个对象通过
[[prototype]]链接使用其prototype对象的属性和方法。 Object.getPrototypeOf()方法返回给定对象的原型对象。请使用Object.getPrototypeOf()方法而不是__proto__。

