js原型链继承与构造函数继承的优缺点及解决方案
1. 原型链的概念
ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。
摘自《JavaScript高级程序设计》原型链继承解释
简单的来说 原型链就是一种关系
一种实例对象和原型对象之间的关系,关系是通过原型__proto__ 来联系的
2. 原型链继承
// 实现原型链有一种基本模式,其代码大致如下。
// 第一个构造函数
function SuperType() {
this.theShy = 'niubi'; // shy爹牛逼
}
//第二个构造函数
function SubType() {
this.xiaohu = '2200'; // ps: 一虎之力 奥利给
}
SuperType.prototype.getAbility = function () {
return this.theShy
}
SubType.prototype.shuchu = function () {
return this.xiaohu
}
// SubType的原型继承了 SuperType
// 这样会重写SubType.prototype中constructor中的值 指向SuperType
SubType.prototype = new SuperType();
// 让构造器的指向更改回正确的指向
SubType.prototype.constructor = SubType;
var instance = new SubType();
console.log(instance); // 图1-1
instance.theShy = 'too niubi'
var instance2 = new SubType();
console.log(instance2.theShy) // 'too niubi'
// 通过原型链继承 所有实例公用一套变量 在一个实例中对变量进行了改变 ,则所有实例中的变量都被改变了
console.log(instance.getAbility()); // 'niubi'
图1-1
原型链继承的优缺点
优点:能通过instanceOf和isPrototypeOf的检测
注意:给原型添加方法的语句一定要放在原型替换SubType.prototype = new SuperType();之后
缺点:(1)SuperType中的属性(不是方法)也变成了SubType的prototype中的公用属性,
如上面例子中的theshy属性,被instance修改后 instance2.theshy获得的是被修改后的变量
(2)创建子类型的时候,不能像父类型的构造函数中传递参数。
2.构造函数继承
function SuperType(name) {
this.name = name;
// this.colors = ['red', 'yellow', 'blue'];
}
function SubType(name) {
//可以向父类中传递参数
SuperType.call(this, name)
// console.log(this); //
}
var instance1 = new SubType('letme');
var instance2 = new SubType();
console.log(instance1.name); // 'letme'
console.log(instance2.name); // undefined
构造函数继承的优缺点
// 原理:在子类型构造函数的内部调用超类型构造函数
// 优点:解决了superType中的私有属性变公有的问题,可以传递参数
// 缺点:方法在函数中定义,无法得到复用
3.组合继承
通俗来讲就是用原型链实现对原型属性和方法的继承,用借用构造函数继承来实现对实例属性的继承。
function SuperType(name) {
this.name = name;
this.height = 178;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
// console.log(this.name);
return this.name
}
SuperType.prototype.modified = function(height){
this.height = height
return this.height
}
SuperType.prototype.getHeight = function(){
return this.height
}
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
// 原型继承 SubType的实例继承了SuperType的方法
SubType.prototype = new SuperType();
// 由于重写了SubType的原型 导致SubType的实例的构造器(constructor)
// 指向错误 会指向SuperType 将其矫正指回SubType;
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
alert(this.age);
};
var instance1 = new SubType('knight', 18);
var instance2 = new SubType();
console.log(instance1.modified(180)); //180
console.log(instance2.getHeight()); // 178
console.log(instance1.sayName()); // knight
console.log(instance2.sayName()); // undefined
// 借用了构造函数继承 在每一个实例中都创建了一个新的副本
// 在其中一个实例中修改SuperType中的属性值不会影响另一个实例中的属性值
在这个例子中,SuperType 构造函数定义了两个属性:name 和 colors。SuperType 的原型定义了一个方法 sayName()。SubType 构造函数在调用 SuperType 构造函数时传入了 name 参数,紧接着又定义了它自己的属性 age。然后,将 SuperType 的实例赋值给 SubType 的原型,然后又在该新原型上定义了方法 sayAge()。这样一来,就可以让两个不同的 SubType 实例既分别拥有自己属性——包括 colors 属性,又可以使用相同的方法了。
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式。而且,instanceof 和 isPrototypeOf()也能够用于识别基于组合继承创建的对象。
摘自《JavaScript高级程序设计》组合继承解释
实例对象使用属性或方法的规则
实例对象使用的属性或方法,现在实例中查找,如果有则使用自身的属性或方法,
如果没有,则通过__proto__指向的原型对象 查找方法,找到则使用,
如果找不到则继续向__proto__寻找,直到未找到时报错
构造函数和原型对象和实例对象之间的关系
1.构造函数可以实例化对象
2.构造函数中有一个属性叫prototype,是构造函数的原型对象
3. 构造函数的原型对象(prototype)中有一个constructor 构造器,这个构造器指向的就是自己所在的原型对象所在的构造函数
4. 实例对象的原型对象__proto__ 指向的是该构造函数的原型对象(prototype)
5. 构造函数的原型对象(prototype)中的方法是可以被实例对象直接访问