继承
许多OO语言都支持两种继承方式:接口继承和实现继承。
接口继承只继承方法签名(方法签名其实就是方法的名称和参数组合在一起)
实现继承是继承实际的方法
因为在ECMAScript中,函数没有签名,因为它是弱语言,没有强制参数传了就一定要使用,也没有强制一定要定义参数,更重要的是js函数中传入的是一个arguments对象,无法用参数个数来区别函数,所以函数也不会重载
因此ECMAScript只支持实现继承,主要依靠原型链
原型链
原型链的基本思想就是利用原型让一个引用类型继承另一个引用类型的属性和方法
通过学习创建对象我们知道,每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针
那么如果我们让原型对象1等于对象2的实例,那么原型对象1将包含原型2的指针,相应的,原型2中也包含一个指向构造函数2的指针,然后原型对象2又可以等于对象3的实例,依次类推,层层递进,就构成了实例与原型的链条,这就是原型链的基本概念
function Stype(){
this.name = 'kkk';
}
Stype.prototype.getsValue = function (){
return this.name;
}
function Gtype (){
this.age = 21;
}
//继承了Stype
Gtype.prototype = new Stype();
Gtype.prototype.getgValue = function (){
return this.age;
}
var inst = new Gtype();
console.log(inst.age+inst.getgValue()+inst.getsValue()); //42kkk
上述代码就是Gtype
继承了Stype
,通过创建Stype的实例,并将该实例赋给了Gtype.prototype, 实现的本质是重写Gtype的原型对象,代之以一个新类型的实例,原来存在于Stype实例中的所有属性和方法,现在也存在与Gtype.prototype中了。
新原型的内部多了一个指针,指向Stype的原型
由图可以看出来,inst 指向 Gtype的原型,Gtype的原型又指向Stype的原型
getsValue() 方法仍然还在Stype.prototype 中,但 name 则位于Gtype.prototype 中,这是因为name是一个实例属性,而getsValue() 则是一个原型方法,现在 Gtype.prototype 是Stype 的实例,那么name当然位于该实例中,另外要注意的是,现在 inst.constructor 指向 Stype,因为原来的Gtype.prototype中的constructor 被重写指向了Stype 的缘故
之前笔记里说过读取模式访问一个实例对象时,先在实例中搜索,再去该实例的原型中搜索,现在通过继承,还会沿着原型链继续向上搜索
**每一个对象(除了null)都有一个属性_proto_
,这个属性指向该对象的原型
默认的原型
所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的,因为函数默认原型都会包含一个内部指针,指向Object.prototype,所以继承的最顶端其实就是Object
检测原型和实例的关系
instanceof
操作符,测试实例与原型链中出现过的构造函数
console.log(inst instanceof Object);
console.log(inst instanceof Stype);
console.log(inst instanceof Gtype);
// 结果都为true 因为原型链的关系 inst是他们任何一个类型的实例
isPrototypeOf()
方法,原理同上
用法:Stype.prototype.isPrototypeOf(inst)
返回true
在通过原型链实现继承时,不能使用字面量创建原型方法,因为这样就会重写原型链,这样原来原型指向也不再是要被继承的实例Stype,而是包含一个Object的实例
原型链的问题
原型链的主要问题就是来自包含引用类型值的原型,和原型模式一样,包含引用类型值的原型属性会被所有实例共享,改一个另一个也会改变
原型链的另一个问题就是:在创建子类型的实例时,不能向超类型(被继承)的构造函数传递参数
借用构造函数
就是在子类型构造函数内部调用超类型构造函数,函数就是在特定环境中执行代码的对象
因此我们可以通过apply()和call()在新创建的对象上执行构造函数
在这里我简单再回顾一下apply()与call()方法
主要是改变this对象的指向
要改变的原来的this对象.apply(改变后的this对象指向)
两个方法作用相同,唯一区别在于apply()后面的参数是数组的形式,call()后面的参数只能是单个的数据一个一个传
function Stype(){
this.colors = ['red','green'];
console.log(this) //window
}
function Gtype(){
Stype.call(this); //继承了Stype
console.log(this) //window,但被赋予实例之后,this便指向为实例对象
}
var inst1 = new Gtype();
inst1.colors.push('pink');
console.log(inst1.colors);
var inst2 = new Gtype();
console.log(inst2.colors);
上面代码的原理是首先创建Gtype的实例对象,通过调用call()方法,让Stype的this变为创建的实例,因为每个新实例对象的this都不同,所有他们也拥有自己的colors属性副本
优点:传递参数
借用构造函数可以在子类型构造函数中向超类型构造函数传递参数
function Stype(name){
this.name = name
}
function Gtype(){
Stype.call(this,'kkk'); //继承了Stype,同时传递了参数
this.age = 18;
}
var inst = new Gtype();
console.log(inst.name+inst.age); //kkk18
为了确保Stype构造函数不会重写子类型的属性,可以在调用超类型构造函数后,再添加应该在子类型中定义的属性
问题
还是构造函数模式的老问题,方法都定义在构造函数中,函数复用无从说起,这样超类型的原型中定义的方法,子类型也访问不到,所有类型都只能使用构造函数模式
组合继承(常用)
组合继承,也叫做伪经典继承,就是将原型链和借用构造函数的技术组合在一起,使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承
function Stype(name){
this.name = name;
this.colors = ['red','pink'];
}
Stype.prototype.sayName = function (){
console.log(this.name);
}
function Gtype(name,age) {
//继承属性
Stype.call(this,name);
this.age = age;
}
//继承方法
Gtype.prototype = new Stype();
Gtype.prototype.constructor = Gtype; //这里为了习惯,我们要设置它指向自己原来的构造函数
Gtype.prototype.sayAge = function (){
console.log(this.age);
}
var inst1 = new Gtype('kkk',23);
inst1.colors.push('yellow');
console.log(inst1.colors); //red,pink,yellow
inst1.sayName(); //kkk
inst1.sayAge(); //23
var inst2 = new Gtype('yyy',14);
console.log(inst2.colors); //red,pink
inst2.sayName(); //yyy
组合继承最大的问题就是无论什么样的情况下,都会调用两次超类型的构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
子类型最终会包含超类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性
原型式继承
这个继承的问题和原型模式一样
寄生式继承
寄生式继承与原型式继承思路很相关,创建一个仅用于封装继承过程的函数,该函数在内部以某周方式来增强对象
function crA(orr) {
var clone = Object(orr); //Object()可以不写,可以直接写成 orr
clone.sayHi = function (){
console.log('hi');
console.log(this.name);
}
return clone;
}
var person = {
name : 'kkk',
age : 55
};
var crAperson = crA(person);
crAperson.sayHi(); //hi kkk
在上面代码中,crA函数接受一个参数,也就是作为新对象基础,要被继承的对象
然后把这个对象赋值给clone ,再为clone对象创建新方法sayHi(),最后返回clone对象
然后再把返回结果赋给crAperson,那么现在对象crAperson就拥有上面所有对象的方法和属性
问题 也是不能做到函数服用,和构造函数模式相似。降低效率
寄生组合式继承(常用)(最理想)
寄生组合式继承,就是通过借用构造函数来继承属性,通过原型链的混成形式来继承方法,不必为了指定子类型的原型而调用超类型的构造函数,用寄生式继承来继承超类型的原型,再将结果指定给子类型的原型
function inherPrototype(Gtype,Stype){
var prototype = Object(Stype.prototype); //创建对象
prototype.constructor = Gtype; //增强对象
Gtype.prototype = prototype; //指定对象
}
function Stype(name){
this.name = name;
this.colors = ['red','pink'];
}
Stype.prototype.sayName = function (){
console.log(this.name);
}
function Gtype(name,age){
Stype.call(this,name);
this.age = age;
}
inherPrototype(Gtype,Stype);
Gtype.prototype.sayAge = function (){
console.log(this.age);
}
var oo1 = new Gtype('ooo',10);
oo1.sayName(); //ooo
oo1.sayAge(); //10
这个例子高效在它只调用了一次Stype构造函数,并且避免了在Gtype.prototype上创建不必要的属性