在ES6之前,JavaScript中的继承是通过原型链来实现的;下面对此进行分析:
一、原型链
首先需要明白构造函数、原型、实例之间的关系:
1、每一个构造函数都有一个指向原型对象的prototype指针;
2、原型对象有一个指向构造函数的constructor指针;
3、每个实例都有一个指向原型对象的[[Prototype]]指针;
原型链方法实现继承的基本思想就是:让原型对象等于另一个类型的实例,那么此时的原型对象包含着指向另一个原型的指针,以此类推,实现继承;
实现原型链的一种基本模式如下:
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
};
function SubType() {
this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subproperty;
};
var instance = new SubType();
console.log(instance.getSuperValue());//true
以上代码,实现了SubType对SuperType的继承,这个例子中实例和原型的关系图如下:
这种模式实现的继承也有其缺点:
1、我们的原型会替换成另一个类型的实例,那么之前的实例属性变成了现在的原型属性,为最底层的所有实例共享;
2、创建子类型的实例时,没有办法在不影响所有实例的情况下向超类型的构造函数传递参数;
2、创建子类型的实例时,没有办法在不影响所有实例的情况下向超类型的构造函数传递参数;
基于以上缺点,实际应用中很少单独使用原型链,为解决这一问题,开发人员又提出一种借用构造函数的技术
二、借用构造函数
借用构造函数技术(又叫伪造对象或经典继承)的基本思想很简单:在子类型的构造函数内部调用超类型的构造函数。举例如下:
function SuperType(name) {
this.name = name;
this.friends = ['Alice'];
}
function SubType() {
SuperType.call(this, 'Jack');
this.age = 29;
}
var instance1 = new SubType();
var instance2 = new SubType();
instance1.friends.push('Bob');
console.log(instance1.name + ' ' + instance1.age);//Jack 29
console.log(instance2.name + ' ' + instance2.age);//Jack 29
console.log(instance1.friends);//["Alice", "Bob"]
console.log(instance2.friends);//["Alice"]
由以上代码可以看到:
1、在创建SubType的实例时,在其构造函数内会调用SuperType的构造函数,结果是每个实例都保存着SuperType中属性的一个副本(通过操作friends属性可以得到验证);
2、和原型链方式相比,借用构造函数的最大的优势是可以向父类传递参数(代码中的name参数);
2、和原型链方式相比,借用构造函数的最大的优势是可以向父类传递参数(代码中的name参数);
当然借用构造函数也有问题:函数都在构造函数中定义,那么每个实例都会有一个副本,无法实现函数复用。而且父类原型中的方法对子类不可见;
考虑到构造函数技术的缺点,单独使用也很少见,更多优秀的继承方法请参考:JavaScript继承中篇