摘要:在本教程中,您将了解 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); // true
Code 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 function
Code 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 function
Code 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__); // true
Code language: JavaScript (javascript)
如前所述,您应该使用Object.getPrototypeOf()
方法而不是__proto__
。Object.getPrototypeOf()
方法返回指定对象的原型。
console.log(p1.__proto__ === Object.getPrototypeOf(p1)); // true
Code language: JavaScript (javascript)
另一种获取原型链接的常用方法是通过constructor
属性获得Object.getPrototypeOf()
方法时,如下所示
p1.constructor.prototype
Code 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__
。