JavaScript 原型

摘要:在本教程中,您将了解 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对象之间的关系

JavaScript Prototype

首先,定义一个名为Person构造函数,如下所示

function Person(name) {
    this.name = name;
}Code language: JavaScript (javascript)

在这个例子中,Person()函数接受一个name参数,并将其分配给this对象的name属性。

在幕后,JavaScript 创建了一个新的函数Person()和一个匿名对象

JS prototype- Person type

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]]表示原型链接

JS prototype- Person 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对象

p1Person.prototypeObject.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.prototypeObject.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)
JS prototype-two person objects

p2对象具有与p1对象相同的属性和方法。

总之,当您在prototype对象上定义一个方法时,该方法由所有实例共享。

在单个对象中定义方法

以下在p2对象上定义draw()方法。

p2.draw = function () {
    return "I can draw.";
};
Code language: JavaScript (javascript)

JavaScript 引擎将draw()方法添加到p2对象,而不是Person.prototype对象

JS prototype - object with method

这意味着您可以在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__
本教程是否有帮助?