继承分为接口继承和实现继承
ECMAScript只支持实现继承,这主要是通过原型链实现的
一、原型链
构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型
<script>
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
//继承SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSuperValue = function() {
return this.subproperty;
}
let instance = new SubType();
console.log(instance.getSuperValue()); //false
</script>
以上代码定义了两个类型:SubType和SuperType。
这两个类型分别定义了一个属性和一个方法
这两个类型的主要区别是SubType通过创建SuperType的实例并将其赋给自己的原型SubType,protoype实现了SuperType的继承。
这个赋值重写了SubType最初的原型,将其替换为SuperType的实例
这意味着SuperType实例可以访问的所有属性和方法也会存在于SubType.prototype
这样实现继承之后,代码紧接着又给SubType.prototype,也就是这个SubType的实例添加了一个新方法。
最后创建了SubType的实例并调用了它继承的getSuperValue()方法
1、默认原型
默认情况下,所有引用原型都继承自Object,这也是通过原型链实现的
任何函数的默认原型都是一个Object的实例,这意味着这个实例有一个内部指针指向Object.prototype
SubType继承SuperType,而SuperType继承Object。
在调用instance.toString()时,实际上调用的是保存在Object.prototype上的方法
2、原型与继承方法
原型与实例的关系可以通过两种方式来确定。
第一种方式是使用instanceof操作符,如果一个实例的原型链中出现过相应的构造函数,则instanceof返回true
<script>
console.log(instance instanceof Object); //true
console.log(instance instanceof SuperType); //true
console.log(instance instanceof SubType); //true
</script>
第二种方法是使用isPrototypeOf()方法
原型链中的每个原型都可以调用这个方法:只要原型链中包含这个原型,这个方法就返回true
<script>
console.log(Object.prototype.isPrototypeOf(instance)); //true
console.log(SuperType.prototype.isPrototypeOf(instance)); //true
console.log(SubType.prototype.isPrototypeOf(instance)); //true
</script>
3、关于方法
子类有时候需要覆盖父类的方法,或者增加父类没有的方法。
为此,这些方法必须在原型赋值之后再添加到原型上
<script>
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
//继承SuperType
SubType.prototype = new SuperType();
//新方法
SuperType.prototype.SuperType = function() {
return this.subproperty;
};
//覆盖已有的方法
SuperType.prototype.getSuperValue = function() {
return false;
}
let instance = new SubType();
console.log(instance.getSuperValue()); //false
</script>
SuperType()的实例仍然会调用最初的方法。因为这个两个重写的方法都是在把原型赋值为SuperType的实例之后定义的
以对象字面量方式创建原型方法会破坏之前的原型链,因为这相当于重写了原型链
<script>
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
//继承SuperType
SubType.prototype = new SuperType();
//通过对象字面量添加新方法,这会导致上一行无效
SubType.prototype = {
getSubValue() {
return this.subproperty;
},
someOtherMethod() {
return false;
}
};
let instance = new SubType();
console.log(instance.getSuperValue()); //报错
</script>
在这段代码中,子类的原型在被赋值为SuperType的实例后,又被一个对象字面量覆盖了。覆盖后的原型是一个Object的实例,而不再是SuperType的实例。因此之前的原型链就断了。SuperType和SubType之间就没有关系了
4、原型链的问题
<script>
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {}
//继承SuperType
SubType.prototype = new SuperType();
let instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors);
let instance2 = new SubType();
console.log(instance2.colors);
</script>
在这个例子中,SuperType构造函数定义了一个colors属性,其中包含一个数组(引用值)。
每个SuperType的实例都会有自己的colors属性,包含自己的数组
但是,当SubType.prototype = new SuperType();
后,SubType.prototype变成了SuperType的一个实例,因此也获得了自己的colors属性
这类似于创建了SubType.prototype.colors属性,最终结果是,SubType的所有实例都会共享这个colors属性。
二、盗用构造函数
为了解决原型包含引用值导致的继承问题,一种叫"盗用构造函数的”技术开始流行起来
在子类构造函数中调用父类构造函数。
<script>
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {
//继承SuperType
SuperType.call(this);
}
let instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors);
let instance2 = new SubType();
console.log(instance2.colors);
</script>
1、调用参数
盗用构造参数