继承,跨域,作用域,闭包,this的指向问题等,一直在初学JavaScript朋友的脑海里,很模糊,或者不是很理解很清楚,这几个问题一直是JavaScript中的难点,想要弄透彻,就必须要看好多的资料,才能理解。接下来,我个人对继承那块做了整理和自己的见解,希望能有用。
许多的OO语言都支持两种方式:接口继承与实现继承。
接口继承——>方法签名
实现继承——>实际方法
由于在JavaScript中,没有函数签名,所以就无法实现接口继承,可以是实现的是实现继承,就是依靠原型链实现。
原型链
首先,要理解继承,就必须理解原型链的一些事。如果没有对对象原型那块搞懂,参考JavaScript创建对象的几种方式
简单的讲:利用原型让一个引用类型继承另一个引用类型的属性和方法,说得更为直接就是,一个构造函数,就有一个相对应的原型,然后让另一个构造函数的原型作为这个原型的实例,然后层层递进,就有了实例和原型的链条。
这段代码就实现了继承:
function F1(){
this.isName = true;
}
F1.prototype.show = function(){
return this.isName;
}
function F2(){
this.isAge = false;
}
//继承F1
F2.prototype = new F1();
F2.prototype.shoewOne = function(){
return this.isAge;
}
var list = new F2();
alert(list.show()); //true
图解原型连:
从该例子就可以看出继承。F1是一个构造函数,里面有一个属性,构造函数的原型里定义了一个方法show(),在F2中也是一样的,只不过,F2继承了F1,F2的原型是F1原型的实例,上面的箭头指向就可以深深地理解原型链。
在原形链上找list.show()大概需要以下几个过程:
1.搜索实例,如果有,输出,没有继续2
2.搜索F2.prototype(F2的原型)
3.搜索F1.prototype,最后找到了。
如果找不到的话,一直找下去,一环一环,找到原型链末端才会停下来。
其实,在原型链上,所有的应用类型都继承Object,也就是原型链末端,所有的原型都是Object的实例,这也正是能使用toString()和valueOf()方法的原因。
注意:子类型有时候需要覆盖超类型中的某个方法,或者需要添加某个超类型不存在的某个方法。但是不管怎么样,给原型添加方法的代码一定要放在替换原型的语句之后。
function F1(){
this.isName = true;
}
F1.prototype.show = function(){
return this.isName;
}
function F2(){
this.isAge = false;
}
//继承F1
F2.prototype = new F1();
//添加一个新的方法
F2.prototype.shoewOne = function(){
return this.isAge;
}
//重写超类型中的方法
F2.prototype.show = function(){
return false;
}
var list = new F2();
alert(list.show()); //false
当通过F2的实例调用show()时,调用的就是这个重新定义的方法,但是通过F1的实例调用show()时,还会继续调用原来的方法。
但是,必须在用F1的实例替换原型后,在定义那两个方法。
在原型链继承的时候,不能使用对象对象字面量创建原型方法,因为这样子,会切断原型链。
问题:
function F1(){
this.colors = ["red","blue","green"];
}
function F1(){}
//继承F1
F2.prototype = new F1();
var list = new F2();
list.colors.push("black");
alert(list.colors); //red,blue,green,black
var list2 = new F2();
alert(list.colors); //red,blue,green,black
从这个代码就可以看出,引用类型的值会被所有实例共享,原型链也是一样,还有一个问题就是,在创建子类型的实例时,不能向超类型的构造函数中传递参数。应该可以说的是,没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。
构造函数原型链
这种构造函数的思想就是在子类型的构造函数的内部调用超类型构造函数。函数只不过是在特定环境下执行代码的对象,因此呀,就可以使用apply()和call()方法来创建的新对象上的执行构造函数。
function F1(){
this.colors = ["red","blue","green"];
}
function F2(){
//继承了F1
F1.call(this);//在这里,调用了F1的构造函数
}
var list = new F2();
list.colors.push("black");
alert(list.colors); //red,blue,green,black
var list2 = new F2();
alert(list.colors); //red,blue,green
通过使用call()或者apply()方法,实际上是在新创建的F2实例的环境下调用了F1构造函数,这样,就会在F2对象上执行F1()函数中定义的所有对象初始化代码,结果,F2的每个实例就都会有自己的colors属性的副本。
但是在这里,函数的服用就无从谈起了!
组合继承
这种方式是将原型链和构造函数结合到一块,使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样子之后,在原型上定义的方法实现了函数复用,又能保证每个实例都有自己的属性。
看代码理解一下:
function F1(name){
this.name = name;
this.colors = ["red","blue","green"];
}
F1.prototype.show = function(){
alert(this.name);
}
function F2(name,age){
//继承了F1属性
F1.call(this,name);
this.age = age;
}
//继承方法
F2.prototype = new F1();
F2.prototype.constructor = F2;
F2.prototype.showOne = function(){
alert(this.age);
}
var list = new F2("Lee",22);
list.colors.push("black");
alert(list.colors); //red,blue,green,black
list.show(); //Lee
list.shoeOne(); //22
var list2 = new F2("Jobas",22);
alert(list.colors); //red,blue,green
list.show(); //Jobas
list.shoeOne(); //22
F1构造函数定义了两个属性:name和colors,F1的原型上定义了一个方法show(),F2在构造函数时传入了name参数,而且也定义了一个age属性。将F1的实例赋值给F2的原型,然后又在该原型上定义了一个shoeOne()。这样子来说,就可以让两个不同的F2实例分别拥有自己的属性,包括colors数组,又可以使用相同的方法。
这种继承的方式是普遍常用的继承方式,也是最为广泛的,所以,这种方式,好好研究。
寄生组合式继承
其实,还有原型式继承,寄生式继承,寄生组合式继承等,这些不太为常用,但是优点十足,可以研究下,这里就只讨论下寄生组合式继承。
上面写了下组合式继承,但是还是有些不足,有些冗余,就是调用了两次构造函数,一次在创建子类型的时候,另一次在子类型构造函数内部。
function F1(name){
this.name = name;
this.colors = ["red","blue","green"];
}
F1.prototype.show = function(){
alert(this.name);
}
function F2(name,age){
//继承了F1属性
F1.call(this,name); //调用了F1()
this.age = age;
}
//继承方法
F2.prototype = new F1(); //调用了F1()
F2.prototype.constructor = F2;
F2.prototype.showOne = function(){
alert(this.age);
}
var list = new F2("Lee",22);
list.colors.push("black");
alert(list.colors); //red,blue,green,black
list.show(); //Lee
list.shoeOne(); //22
var list2 = new F2("Jobas",22);
alert(list.colors); //red,blue,green
list.show(); //Jobas
list.shoeOne(); //22
在第一次调用F1构造函数的时候,F2.prototype会得到两个属性:name和colors;他们有事F1的实例属性,只不过现在位于F2的原型上。当调用F2构造函数时,又会调用一次F1构造函数,这一次又在新对象上创建实例属性name和colors,于是,这两个属性就屏蔽了原型中的两个同名属性。
在寄生组合式继承中,借用构造函数来继承属性,通过原型链的混成形式来继承方法。实质上就是,使用寄生式继承来继承超类型的原型,然后在讲解过指定给子类型的原型。
代码理解:
function fun(F2,F1){
var protoype = Object(F1.prototype);
prototype.constructor = F2;
F2.prototype = prototype;
}
function F1(name){
this.name = name;
this.colors = ["red","blue","green"];
}
F1.prototype.show = function(){
alert(this.name);
}
function F2(name,age){
//继承了F1属性
F1.call(this,name);
this.age = age;
}
//继承方法
fun(F2,F1);
F2.prototype.showOne = function(){
alert(this.age);
}
var list = new F2("Lee",22);
list.colors.push("black");
alert(list.colors); //red,blue,green,black
list.show(); //Lee
list.shoeOne(); //22
var list2 = new F2("Jobas",22);
alert(list.colors); //red,blue,green
list.show(); //Jobas
list.shoeOne(); //22
这是最理想的继承方式。
JavaScript主要通过原型链实现继承,当然最主要的是组合式继承,深入理解,集优点于一身,最为普遍了!