继承的概念:子类可以使用父类共享的属性和方法,避免重复代码提高代码复用性。
原型链:子类可以共享父类的实例对象和实例对象的原型对象;父类继承object,直到找不到为null为止。
Javascript继承详解
1、原型链继承 2、构造函数式继承 3、组合式继承
4、原型式继承 5、寄生式继承 6、寄生组合式继承
1. 类式继承(原型链继承)
将父类对象的实例赋值给子类的原型,则子类的原型可以访问父类原型上的属性和方法,以及父类构造函数中复制的属性和方法。
注意:子类继承的是父类实例的属性方法和实例的原型对象,同时如果子类本身就有名称相同的属性和方法,优先自身的
优缺点简单归纳:
子类可以继承父类实例的属性以及实例原型对象的属性;但是如果属性或者方法是引用类型,那么多个实例在引用的时候,如果前一个修改了数据,会导致别的实例引用的是修改之后的。
js中的数据类型有以下几种:
Number Boolean undefined Object Function String Null
基本类型:Number Boolean String undefined null
引用类型:Object Function
基本类型的数据是存放在栈内存中的,而引用类型的数据是存放在堆内存中的
例1:
//1.类式继承 //声明父类 function SuperClass() {
this.superValue = true;
}
//为父类添加公有方法 SuperClass.prototype.getSuperValue = function () {
return this.superValue;
};
//声明子类 function SubClass() {
this.subValue = false;
}
//继承父类 SubClass.prototype = new SuperClass();//将父类对象赋值给子类原型,子类原型可访问父类原型上的属性和方法--类式继承原理 //子类添加公有方法 SubClass.prototype.getSubValue = function() {
return this.subValue;
};
//测试 var instance = new SubClass();
console.log(instance.getSuperValue();//t console.log(instance.getSubValue());//f // console.log(instance instanceof SuperClass);//t console.log(instance instanceof SubClass);//t console.log(SubClass instanceof SuperClass);//f console.log(SubClass.prototype instanceof SuperClass);//t 案例解析function SuperClass(){
this.superValue = true;
} //父function SubClass(){} //子SubClass.prototype = new SuperClass(); //subclass的原型对象指向Superclass的实例对象SubClass.prototype.getSubValue = function(){} //再修改了sub的原型后,添加了一个新的原型方法
缺点:
1.子类通过其原型prototype对父类实例化,继承了父类。但当父类中的共有属性是引用类型时,会在子类中被所有的实例共用,如此在一个子类实例中更改从父类中继承过来的公有属性时,会影响到其他子类。
2.由于子类是通过原型prototype实例化父类实现继承的,所以在创建父类的时候,无法向父类传递参数,因而在实例化父类的时候无法对父类构造函数内的属性初始化。
例2:
function SuperClass() {
this.book = ['javascript','html','css'];
}
function SubClass() {}
SubClass.prototype = new SuperClass();
var ins1 = new SubClass();
var ins2 = new SubClass();
console.log(ins1.book);
console.log(ins2.book);
ins1.book.push("Node");
//引用类型在子类中被所有实例共用 console.log(ins1.book);//[ 'javascript', 'html', 'css', 'Node' ] console.log(ins2.book);//[ 'javascript', 'html', 'css', 'Node' ]
例3:
function SuperClass(){
this.name="老爹";
this.age=50;
this.jihe=[1,2,3];
}
SuperClass.prototype.sayHello=function(){
console.log("大家好,我是老爹");
}
function SubClass(){
this.name="儿子";
}
//原型链继承的写法:
SubClass.prototype=new SuperClass();
var Sub1=new SubClass();
var Sub2=new SubClass();
Sub1.jihe.push("4");
//如果子类本身就有名称相同的属性和方法,优先自身的
console.log(Sub1.name);
//子类继承父类,可以共享父类实例的属性方法以及实例的原型的属性和方法
console.log(Sub1.sayHello);
console.log(Sub1.age);
//当父类中的共有属性是引用类型时,会在子类中被所有的实例共用,如此在一个子类实例中更改从父类中继承过来的公有属性时,会影响到其他子类。
console.log(Sub1.jihe); //[1, 2, 3, "4"]
console.log(Sub2.jihe); //[1, 2, 3, "4"]
======================================================================================================================
2. 构造函数式继承
通过在子类的构造函数中执行一次父类的构造函数实现。
优缺点归纳:
不会像原型链继承那样出现引用类型属性互相占用的问题,但是无法共享实例的原型对象。
//2.构造函数式继承 //声明父类 function SuperClass(id) {
this.book = ['javascript','html','css'];//引用类型共有属性 this.id = id;//值类型公有属性 }
//父类声明原型方法 SuperClass.prototype.showBooks = function() {
console.log(this.books);
}
//声明子类 function SubClass(id) { //继承父类 SuperClass.call(this,id); //使用调用call函数的内部结构而不使用它的原型 } //测试 var ins1 = new SubClass(1);
var ins2 = new SubClass(2);
ins1.book.push("Node");
console.log(ins1.id);//1 console.log(ins1.book);//['javascript', 'html', 'css', 'Node'] console.log(ins2.id);//2 console.log(ins2.book);//['javascript', 'html', 'css'] ins1.showBooks();//TypeError: ins1.showBooks is not a function
**SuperClass.call(this,id)**是构造函数式继承的中心。call方法可以改变函数的作用环境,在子类中调用这个方法就是将子类中的变量在父类中执行,父类中给this绑定属性,因而子类继承了父类的共有属性。
关键:
为什么继承方式1会出现bug?
SubClass.prototype = new SuperClass();
SuperClass的函数中声明了引用类型的数组Book,那么new SuperClass()作为SuperClass函数的实例,就具有了该属性
function SuperClass(){this.book=[1,2,3]}
SubClass.prototype = new SuperClass(); //完成继承var a = new SubClass();var b = new SubClass();Object { … }关于call()function A(){
this.name="a";
this.test = function(){alert(this.a)}
}function B(){
A.call(this);// this.name="a";
this.test = function(){alert(this.a)}
}var b = new B();
b.test();
构造函数式继承的缺点:
这种类型的继承没有涉及原型prototype,只是复制构造函数实例里面的对象,所以父类的原型方法不会被子类继承。如想被子类继承就必须放在构造函数中,这样创造的每个实例都会单独拥有一份而不能共用,违背了代码复用原则。
例:
function SuperClass(){
this.name="老爹";
this.age=50;
this.jihe=[1,2,3];
}
SuperClass.prototype.sayHello=function(){
console.log("大家好,我是老爹");
}
function SubClass(){
this.name="儿子";
}
//构造函数式继承的方法:
function SubClass(){
SuperClass.call(this);
}
var Sub1=new SubClass();
//这种类型的继承没有涉及原型prototype,只是复制构造函数实例里面的对象,所以父类的原型方法不会被子类继承。如想被子类继承就必须放在构造函数中,这样创造的每个实例都会单独拥有一份而不能共用,违背了代码复用原则。
console.log(Sub1.age); //50
console.log(Sub1.sayHello); //underfined
====================================================================================================================================
3. 组合式继承
综合以上两种模式的优点,在子类原型上实例化父类,在子类构造函数中执行一遍父类的构造函数。这样融合了类式继承和构造式继承的优点,过滤了缺点。(解决了原型链继承的缺点。)
优点归纳:
//3.组合式继承 function SuperClass(name) {
this.name = name;
this.book = ['javascript','html','css'];
}
SuperClass.prototype.getName = function () {
console.log(this.name);
};
function SubClass(name,time) {
//构造函数式继承,继承父类name属性 SuperClass.call(this,name);
this.time = time;
}
//类式继承,子类原型继承 SubClass.prototype = new SuperClass();
//子类原型方法 SubClass.prototype.getTime = function () {
console.log(this.time);
};
//测试 var ins1 = new SubClass('Node',2016);
ins1.book.push("Node");
console.log(ins1.book);
ins1.getName();
ins1.getTime();
var ins2 = new SubClass('React',2015);
console.log(ins2.book);
ins2.getName();
ins2.getTime();
组合继承的缺点:
父类的构造函数执行了两遍:一次在子类的构造函数中call方法执行一遍,一次在子类原型实例化父类的时候执行一遍。
例:
//组合继承=构造函数式继承+原型链继承
function SuperClass(){
this.name="老爹";
this.age=50;
this.jihe=[1,2,3];
}
SuperClass.prototype.sayHello="大家好,我是老爹";
//构造函数式继承
function SubClass(){
SuperClass.call(this);
}
//原型链继承
SubClass.prototype=new SuperClass();
var Sub1=new SubClass();
var Sub2=new SubClass();
//解决了原型链继承的缺点:即使是引用类型,改变了Sub1,Sub2没有更改
Sub1.jihe.push("4");
console.log(Sub1.jihe); //[1, 2, 3, "4"]
console.log(Sub2.jihe); //[1, 2, 3]
//解决了构造函数式继承的缺点:可以共享原型
console.log(Sub1.sayHello); //大家好,我是老爹
==================================================================
3种继承方式总结:
1、原型链继承:通过修改子构造的原型对象指向父构造实例
缺点: 引用类型属性可能会存在互相占用的问题
优缺点简单归纳:
子类可以继承父类实例的属性以及实例原型对象的属性;但是如果属性或者方法是引用类型,那么多个实例在引用的时候,如果前一个修改了数据,后导致别的实例引用的是修改之后的。
SubClass.prototype=new SuperClass();
2、构造函数式继承:复制对象来实现继承:父类构造函数.call(子类实例)
缺点: 原型无法共享,因为是复制对象而不是直接引用对象故而原型对象无法完成全局共享
优缺点归纳:
不会像原型链继承那样出现引用类型属性互相占用的问题,但是无法共享实例的原型对象。
function SubClass(){
SuperClass.call(this);
}
3、组合式继承: 将1和2组合在一起,既复制父类的具体实例,也引用父类的具体实例做为原型,从而产生原型链来实现原型继承
function SubClass(){
SuperClass.call(this); //实现构造函数的内部复制}
SubClass.prototype=new SuperClass(); //指定原型链
======================================================================================================================
4.原型式继承
对类式继承(原型链继承)的封装,过渡对象相当于子类。
问题也是一样的:
1、引用数据冲突
2、子类固定了
//4.原型式继承
//首字母小写,不是构造函数
//工厂模式:生产对象的一种函数
function inheritObject(o) { //传的是父实例
//声明过渡函数对象 function F() {} //匿名子类
//过渡对象的原型继承父类 F.prototype = o;
return new F();
}
//测试 var book = {
name : "javascript",
book : ['js','css']
};
var newbook = inheritObject(book);
newbook.name = "ajax";
newbook.book.push("Node");
var otherbook = inheritObject(book);
otherbook.name = "xml";
otherbook.book.push("React");
console.log(newbook.name);//ajax console.log(newbook.book);//[ 'js', 'css', 'Node', 'React' ] console.log(otherbook.name);//xml console.log(otherbook.book);//[ 'js', 'css', 'Node', 'React' ] console.log(book.name);//javascript console.log(book.book);//[ 'js', 'css', 'Node', 'React' ]
5.寄生式继承
寄生式继承其实是对原型继承的第二次封装,并且在第二次封装的过程中对继承的对象进行了拓展。解决了子类自己对象拓展
//5.寄生式继承 function inheritObject(o) {
//声明过渡函数对象 function F() {}
//过渡对象的原型继承父类 F.prototype = o;
return new F();
}
//声明基对象 var book = {
name : "javascript",
book : ['js','css']
};
function createBook(obj) {
//通过原型继承方式创建新对象 var o = new inheritObject(obj);
//拓展新对象 o.getName = function() {
console.log(name);
}
//返回拓展后的新对象 return o;
}
var newbook = createBook(book);
newbook.name = "ajax";
newbook.book.push("Node");
var otherbook = createBook(book);
otherbook.name = "xml";
otherbook.book.push("React");
console.log(newbook.name);//ajax console.log(newbook.book);//[ 'js', 'css', 'Node', 'React' ] console.log(otherbook.name);//xml console.log(otherbook.book);//[ 'js', 'css', 'Node', 'React' ] console.log(book.name);//javascript console.log(book.book);//[ 'js', 'css', 'Node', 'React' ]
6. 寄生式组合继承
//寄生组合式继承 function inheritObject(o) {
//声明过渡函数对象 function F() {}
//过渡对象的原型继承父类 F.prototype = o;
return new F();
}
//寄生式继承 继承原型 function inheritPrototype(subClass,superClass) {
//复制一份父类的原型副本保存在变量中 var p = inheritObject(superClass.prototype);
//修正因为重写子类原型导致子类的constructor属性被修改 p.constructor = subClass;
//设置子类的原型 subClass.prototype = p;
}
function SuperClass(name) {
this.name = name;
this.colors = ["red","blue","green"];
}
//定义父类原型方法 SuperClass.prototype.getName = function() {
console.log(this.name);
}
function SubClass(name,time) {
SuperClass.call(this,name);
this.time = time;
}
//寄生式继承父类原型 inheritPrototype(SubClass,SuperClass);
//子类新增原型方法 SubClass.prototype.getTime = function() {
console.log(this.time);
}
//测试 var ins1 = new SubClass("js",2014);
var ins2 = new SubClass("css",2015);
ins1.colors.push("black");
console.log(ins1.colors);
console.log(ins2.colors);
ins2.getName();
ins2.getTime();
写组件,参考jquery EasyUI