# 原型和原型链

# 原型

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

# 三个属性

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

# 举个🌰

创建了一个函数后,默认情况下,其原型对象只获得一个 constructor 属性,该属性指向 Person 函数。

// 构造函数
function Person() {}
console.log(Person.prototype);
1
2
3

proto1

可以给原型对象添加属性和方法

// 构造函数
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);
1
2
3
4
5
6
7
8
9

proto2

每个对象实例都有一个 __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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

proto3

它们之间的关系如下图: proto5

由于原型对象会共享自身的属性和方法,因此这里创建的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);
1
2
3
4
5
6
7
8
9
10
11
12

proto4

可以看出:只是为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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

proto6

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