原型和原型链

190 阅读3分钟

原型

概念

  • 无论什么时候,只要创建了一个函数,那么就会为之创建一个prototype属性,这个属性指向了函数的原型对象
  • 在默认情况下,每个原型对象同样会创建一个constructor属性,用于指向prototype属性所在函数的指针。
  • 使用构造函数创建的对象,会创建一个名为[[prototype]]的指针指向构造函数的原型对象

三个属性

  • prototype:每一个函数都有一个prototype属性,指向原型对象。
    • 例外:Function.prototype.bind()没有prototype属性
  • [[prototype]:每一个实例对象都有一个[[prototype]]内部属性,指向构造函数的原型对象。
  • constructor:每一个原型对象都有一个constructor属性,指向关联的构造函数。

举个🌰

  • 创建了一个函数后,默认情况下,其原型对象只获得一个constructor属性,该属性指向Person函数。
// 构造函数
function Person() {}
console.log(Person.prototype);

  • 可以给原型对象添加属性和方法
// 构造函数
function Person() {}
Person.prototype.country = 'China';
Person.prototype.sex = 'man';
Person.prototype.age = 26;
Person.prototype.sayAge = function() {
	console.log(this.age);
}
console.log(Person.prototype);

  • 每个对象实例都有一个__proto__属性去访问原型对象,这里的__proto__属性是浏览器提供用于访问实例对象内部属性[[prototype]]。ECMAScript5中增加了一个Object.getPrototypeOf()方法,用于对象访问其原型对象。
// 构造函数
function Person() {}
Person.prototype.country = 'China';
Person.prototype.sex = 'man';
Person.prototype.age = 26;
Person.prototype.getAge = function() {
	console.log(this.age);
}

var person1 = new Person();
person1.getAge();
console.log(person1);

var person2 = new Person();
person2.getAge();
console.log(person2);

  • 它们之间的关系如下图:
  • 由于原型对象会共享自身的属性和方法,因此这里创建的2个Person对象实例能够直接访问getAge()
    • 当调用person1.getAge()时,会先在person1实例中搜索getAge(),若找到就返回;若没有找到,则到person1的原型对象中去搜索。这里进行了2次搜索工作。
    • 若在实例对象中添加了原型对象中同名的属性,那么该属性并不会重写原型对象中的值,只是作为实例对象自己的属性,不会被共享。例如:
// 构造函数
function Person() {}
Person.prototype.country = 'China';
Person.prototype.sex = 'man';
Person.prototype.age = 26;
Person.prototype.getAge = function() {
	console.log(this.age);
}

var person1 = new Person();
person1.age = 28;
console.log(person1);

  • 可以看出:只是为person1对象添加了一个age属性,而原型对象中的age属性的值不改变。
  • console.log(person1.age)会输出28,而不是26。此时访问的是person1对象中的age属性。可以使用delete person1.age删除本对象上的属性,删除之后访问的就是原型对象上面的age属性了。
  • 可以使用hasOwnProperty()来检测一个属性是存在于对象实例中,还是原型对象中。

原型链

概念

  • 当一个实例对象的原型对象是另一种类型的实例对象,且这个实例对象的原型对象同样又是另一种类型的实例对象,以此下去,最终会就形成了一个有限的原型链。
  • 原型链是JavaScript中一种能够实现继承的方式。

举个🌰

function Animal() {}
Animal.prototype.category = 'Animal';

function Cat() {}
// Cat的原型对象赋值为Animal实例对象
Cat.prototype = new Animal();
Cat.prototype.category = 'Cat';

function Tiger() {}
// Tiger的原型对象赋值为Cat实例对象
Tiger.prototype = new Cat();
Tiger.prototype.category = 'Tiger';

var tiger = new Tiger();
console.log(tiger);

  • tiger指向Tiger的原型,Tiger的原型指向Cat的原型,Cat的原型指向Animal的原型,Animal的原型指向Object的原型。
  • 简单来说:__proto__属性连接每个对象,形成了原型链。任何原型链的最底层都是Object.prototype
  • 注意:Tiger实例对象和Cat实例对象的原型对象都没有constructor属性,这是因为我们重写了原型对象,将其赋值为了使用new创建的新对象。