一、简述三者的关系
每创建一个函数,该函数都会自动带有一个prototype
属性。该属性是一个指针,指向一个对象,该对象称之为原型对象(后期我们可以使用这个原型对象帮助我们在js中实现继承).
原型对象上默认有一个属性constructor
,该属性也是一个指针,指向其相关联的构造函数。
通过调用构造函数产生的实例对象,都拥有一个内部属性,指向了原型对象。其实例对象能够访问原型对象上的所有属性和方法。下面用代码和图示来表示三者的关系:
let ldh = new Star();
console.log(ldh.__proto__);//指向的Star的原型对象prototype
console.log(ldh.__proto__.__proto__);//指向的Star的原型对象的对象原型Object
console.log(ldh.__proto__.__proto__.__proto__);//指向的Star的原型对象的对象原型Object的对象原型null
输出结果如下:
第一个输出的是Star的原型对象prototype
,第二个输出的是Star原型对象的对象原型,因为Star是直接定义的function所以它的对象原型是Object
,第三个输出的Object的队形原型,因为Object已经是最终了,所以它的对象原型指向为null
。用图示的方式可能更加直观。
三者的关系是,每个构造函数都有一个原型对象,原型对象上包含着一个指向构造函数的指针,而实例都包含着一个指向原型对象的内部指针。通俗的说,实例可以通过内部指针访问到原型对象,原型对象可以通过constructor
找到构造函数。
二、组合继承
ES6之前没有给我们提供extends继承。我们可以通过构造函数
+原型对象
模拟实现继承,称为组合继承。这也是原型对象和构造函数的用处之一。
function Father(uname, age) {
this.uname = uname;
this.age = age;
}
function Son(uname, age, score) {
Father.call(this, uname, age);
this.score = score;
}
//利用父类原型对象实现子类的继承
Son.prototype = new Father();
Son.prototype.constructor = Son;
//在继承父类原型对象的基础上增添子类独有的方法
Son.prototype.exam = function exam() {
console.log('儿子要考试');
};
var son = new Son('刘德华', 18, 100);
son.exam();
console.log(son);
console.log(Father.prototype);
console.log(Son.prototype.constructor);
运行结果如图所示:
输出的儿子要考试表示Son独有的方法增加成功,在son的定义中并没有uname和age第二个输出的son对象中却包含在内,表示son成功继承Father的属性,第三个输出father的原型对象指向的仍然还是father的原型对象,第四个输出的是son的构造函数。通过这种方法,就实现了子类继承父类,在后来的Es6中通过extends关键字继承父类,表面上看起来方便很多,其实内部原理都异曲同工。