一、3个基础知识
在理解原型和原型链之前,需要知道几个重要的前提知识,这样利于理解后面的内容。
- 每个函数(箭头函数除外)都有
prototype属性,且只有函数有,普通对象没有。 - 所有对象都有
__proto__属性。因为函数是特殊的对象,因此函数既有prototype也有__proto__属性。 obj.__proto__、Object.getPrototypeOf(obj)、obj[[Prototype]]这三者都是指对象obj的原型,也称原型对象、实例原型。
二、3个重要属性和原型
1. prototype
(1)函数(尤其是构造函数)自带一个 prototype 属性,这个属性指向该函数的原型,这个原型也是对象,因此叫原型对象。
(1)
prototype是“可作为构造函数的函数” 才有的属性,普通对象如{}、[]、new Object()创建的对象没有prototype属性,访问obj.prototype会返回undefined。
(2)函数对象如function fn() {}、class声明的类有prototype属性,因为函数可作为构造函数,prototype用于给实例提供原型。
function Person() {}
console.log(Person.prototype); // 存在,是一个对象
// 函数的 prototype 属性 → 原型对象(模板)
Person.prototype.sayHi = function() {
console.log(`你好,我是${this.name}`);
};
const obj = {};
console.log(obj.prototype); // undefined
const arrow = () => {};
console.log(arrow.prototype); // undefined(箭头函数)
2. __proto__属性
(1)实例对象通过构造函数创建,并通过__proto__ 指向该实例对象的原型。
(2)函数是特殊的对象,因此也有__proto__属性,函数的__proto__指向问题3.2会详细说明。
(3)虽然__proto__是非ECMAScript标准的,但是许多 JavaScript引擎实际上实现了它,因此使用obj.__proto__可以访问到原型对象。现在的规范建议使用Object.getPrototypeOf(obj)来获取原型对象。
function Person() {}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
console.log(person.__proto__ === Object.getPrototypeOf(person)) // true
3. constructor属性
- 对于原型对象来说,它有个
constructor属性,指向它的构造函数。
- 对于实例对象来说,实例对象本身没有
constructor属性,但是person.constructor是有值的,因为它会沿着原型链向原型对象查找,而原型对象是有constructor属性的,因此person.constructor就是Person.prototype.constructor。
function Person() {}
var person = new Person()
Person.prototype.constructor === Person // true
person.constructor === Person.prototype.constructor // true
三、原型链
3.1 原型链相关知识
- 由于对象的构造函数是
Object,所以对象的原型对象,就是Object.prototype,Object.prototype该对象比较特殊,它没有上一层的原型对象,它的__proto__指向的是null。 - 每个对象通过
__proto__指向该对象的原型,而原型也是对象,因此也通过__proto__指向自己的原型,以此类推,直到原型是null的对象(原型链的尽头Object.prototype)。这种一层一层指向的关系就是原型链。 - 基于以上,在获取实例对象的属性时,先访问自身的属性,如果有就返回,如果没有就向原型访问该属性,如果原型也没有该属性,就向原型的原型访问该属性,直到找不到为止,其实当到
Object.prototype就可以停止查找了。
// 获取实例对象自身属性(方法)而非继承来的使用以下两个方法
obj.hasOwnProperty('属性名')
Object.hasOwn(obj, '属性名');
function Graph() {
this.vertices = [];
this.edges = [];
}
Graph.prototype.addVertex = function (v) {
this.vertices.push(v);
};
const g = new Graph();
// 自有属性,返回true
g.hasOwnProperty("vertices"); // true
Object.hasOwn(g, "vertices"); // true
// 自有属性没有该属性,返回false,且不向原型链读取
g.hasOwnProperty("nope"); // false
Object.hasOwn(g, "nope"); // false
// 原型对象的方法,非自有,返回false
g.hasOwnProperty("addVertex"); // false
Object.hasOwn(g, "addVertex"); // false
// 原型对象本身含有'addVertex'方法,返回true
Object.getPrototypeOf(g).hasOwnProperty("addVertex"); // true
// 因此不能使用“属性是否为undefined”来检查属性,因为属性名很可能就是'undefined',使用以上两个是最规范安全的
3.2 函数原型相关知识点
函数自带prototype属性,并且函数也是一种特殊的对象,因此也有__proto__属性。函数的原型func.__proto__指向什么呢?
- 在JavaScript中,
Function是所有函数的构造函数,Array、Object、String以及上面定义的Person等函数都继承于Function。并且因为Function是构造函数,也有自己的实例,它的实例是Function()。 - 知道了所有函数的构造函数都是
Function之后,func.__proto__的指向就是Function.prototype。由于Function本身也是函数,因此Function.__proto__的指向也是Function.prototype。 - 此外,
Function.prototype也是对象,它的原型对象Function.prototype.__proto__指向原型链的尽头Object.prototype。
对于第2点和第3点,就会出现一个
Function和Object鸡和蛋的问题,就是先有哪个的问题。首先应该明确就是先有Object.prototype,然后再有Function.prototype,可以理解为Function.prototype是“内置”的原型一样,然后其他所有构造函数都继承于Function.prototype。
所以,完整的原型链图如下(图源:www.mollypages.org/tutorials/j…