前言
对于很多前端开发者而言,JavaScript的原型实在是很让人头疼。
说起原型,那就不得不说prototype、__proto__、constructor三者的关系,其中比较麻烦的就是容易将prototype和__proto__混淆,这也是因为它们之间的指向确实有些复杂。
prototype
每个函数都有一个原型prototype属性,每一个实例对象都会从原型”继承”属性。
代码:1
2
3
4
5
6
7
8
9
10
11
12//Person构造函数
function Person() {
name = 'Person';
this.height = '160cm';
}
//在其原型对象中添加age属性
Person.prototype.age = '18';
//Person的实例p1
var p1 = new Person();
p1.age;//18
p1.name;//undefined
p1.height;//160cm
代码中原型与构造函数与实例的关系如下:
- Person为构造函数
- Person.prototype为原型
- p1虽然是通过newPerson获得的,但是p1的原型__proto__却直接指向Person.Prototype,并且只得到了Person中的this的属性。
var p1 = new Person()都发生了什么?
1.var p1 = new Object(); //此时p1.__proto__ === Object.Prototype
2.p1.__proto__ === Person.prototype;
3.Person.call(p1);//使用新对象p1调用函数Person,将this指向p1
__proto__
实例中的属性,指向new这个实例的构造函数的原型对象
1
2
3
4
5 function Person() {
}
var p1 = new Person();
console.log(p1.__proto__ === Person.prototype); // true
由于__proto__是任何对象都有的属性,而JS中万物皆对象,所以会形成一条__proto__连起来的链条,递归访问__proto__必须最终到头,并且值是null。
当JS引擎查找对象的属性时,先查找对象本身是否存在该属性,如果不存在,会在原型链中查找,但不会查找自身的prototype
代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//Person构造函数
function Person(){
name = 'Person';
this.height = '160cm';
}
//原型为一个Object实例,并有age属性
Person.prototype = {
age: '18'
}
//Person的实例p1
var p1 = new Person();
p1.age;//18
p1.name;//undefined
p1.height;//160cm
console.log(p1 === Person.prototype.constructor); // false
代码中原型与构造函数与实例的关系如下:
- 代码第7行Person.Prototype赋值定义一个新对象Object,并且其中不再有constructor属性
//console.log(p1 === Person.prototype.constructor); // false构造函数原型属性定义给新对象要想constructor重新指向该构造函数,可以这样
Person.prototype.constructor === Person,这样构造函数Person的this指向始终指向Person的实例 - Person.Prototype重新定义Object的一个实例,也就是说Person.Prototype中有一个__proto__属性,指向Object.Prototype
- 在实例p1中想要调用一个方法或者属性的时候会沿原型链__proto__向上查找。
直到Object.prototype.proto === null,停止查找
constructor
既然实例对象(p1.__proto__)和构造函数(Person.Prototype)都可以指向原型,那么原型是否有属性指向构造函数或者实例呢?
指向实例倒是没有,因为一个构造函数可以生成多个实例,但是原型指向构造函数(Person.Prototype.constructor)倒是有的,这就要讲到第三个属性:constructor,每个原型都有一个 constructor 属性指向关联的构造函数。
代码:1
2
3
4function Person() {
}
console.log(Person === Person.prototype.constructor); // true
总结
综上我们已经得出:
- prototype:构造函数中的属性,指向该构造函数的原型对象。
- constructor:原型对象中的属性,指向该原型对象的构造函数
- __proto__:实例中的属性,指向new这个实例的构造函数的原型对象
1
2
3
4
5
6
7
8
9
10function Person() {
}
var p1 = new Person();
console.log(Person.prototype == Person.prototype) // true 构造函数中的属性,指向该构造函数的原型对象。
console.log(p1.__proto__ == Person.prototype) // true 实例中的属性,指向new这个实例的构造函数的原型对象
console.log(Person.prototype.constructor == Person) // true 原型对象中的属性,指向该原型对象的构造函数
// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(p1) === Person.prototype) // true