浅谈JS的继承
JS继承
继承是OO语言中最为人津津乐道的概念,许多OO语言都支持两种方式的继承:接口继承;实现继承。
接口继承:只继承方法签名。
实现继承:继承实际的方法。
由于ES里函数没有签名,所以在ES里面无法实现接口继承,ES只支持实现继承。
——《js高程》
根据高程上面的介绍,结合自己的理解,来谈一谈js中的继承。
1.原型链
基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
function car() { this.name = name || '车'; this.Oncar=['小明','小红'];//车上的人 this.drive =function(){ console.log(this.name +'在行驶'); } } function dazhong() { this.V = 200; this.chesu = function() { console.log('车速是'+this.V+'km/h'); } } dazhong.prototype = new car();//继承 var Dz1 = new dazhong(); Dz1.name = '大众suv'; Dz1.drive();//大众suv在行驶 Dz1.Oncar.push('大白');//为大众suv加一个乘客 console.log(Dz1.Oncar);//["小明", "小红", "大白"] var Dz2 =new dazhong(); Dz2.name = '冒牌大众'; Dz2.drive();//冒牌大众在行驶 console.log(Dz2.Oncar);//["小明", "小红", "大白"] 冒牌大众上居然也共享了这个乘客!
利用原型链的方法,我们可以方便的实现继承,但是有一个问题,那就是 所有的实例共享了引用类型的值!
只是引用类型的值共享了,实例中非引用类型的值并没有共享。(Dz1.drive和Dz2.dirve 不一样);
为了解决原型中包含引用类型值所带来的问题,又推出了另外一种方法: 借用构造函数(constructor stealing)
2.借用构造函数
基本思想是 在子类型构造函数的内部调用超类型构造函数。
函数只不过是在特定环境中执行代码的对象,因此通过使用apply()和call()方法也可以在新创建的对象上执行构造函数。
function dazhong() { car.call(this);//继承 this.V = 200; this.chesu = function() { console.log('车速是'+this.V+'km/h'); } } var Dz1 = new dazhong(); Dz1.name = '大众suv'; Dz1.drive();//大众suv在行驶 Dz1.Oncar.push('大白');//为大众suv加一个乘客 console.log(Dz1.Oncar);//["小明", "小红", "大白"] var Dz2 =new dazhong(); Dz2.name = '冒牌大众'; Dz2.drive();//冒牌大众在行驶 console.log(Dz2.Oncar);//["小明", "小红"]
通过这种方法,可以在子类型构造函数里向超类型构造函数里传值啦。
function car(driver) { this.name = name || '车'; this.Oncar=['小明','小红'];//车上的人 this.driver = driver; this.drive =function(){ console.log(this.name +'在行驶'); } } function dazhong() { car.call(this,'小明');//继承 this.V = 200; this.chesu = function() { console.log('车速是'+this.V+'km/h'); } } var dz1 = new dazhong(); console.log('司机是'+ dz1.driver);//司机是小明 var dz2 =new dazhong(); dz2.driver = '小红'; console.log('司机是'+ dz1.driver);//司机是小明 console.log('司机是'+ dz2.driver);//司机是小红 console.log(dz1 instanceof car);//false dz1 并不是 car 的实例 console.log(dz1 instanceof dazhong);//true dz1 是 dazhong 的实例
借用构造函数的办法虽然解决了传值和引用类型不共享的问题,但是他依然存在着构造函数模式存在的问题——函数无法复用。
方法都写到了构造函数中,对内存是一种损耗。 所以这时候又引出了另外一种方法: 组合继承
3.组合继承
组合继承其实是将之前的两种继承方式结合起来,各取其长的继承方式。
核心思想是 使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性(每个实例各自不同的东西)的继承。
function car(driver) { this.name = name || '车'; this.Oncar=['小明','小bai'];//车上的人 this.driver = driver; } car.prototype.drive = function() { console.log('司机'+this.driver+'驾驶着'+this.name+',在高速公路上'); }; function dazhong(driver,v) { car.call(this,driver);//继承 this.V = v || 0; } // 继承 dazhong.prototype = new car(); dazhong.prototype.constructor = dazhong; dazhong.prototype.chesu = function() { console.log('车速是'+this.V+'km/s'); }; var dz1 = new dazhong('小明',100); dz1.name = '斯柯达'; dz1.Oncar.push('小黑');//["小明", "小bai", "小黑"] dz1.drive();//司机小明驾驶着斯柯达,在高速公路上 dz1.chesu();//车速是100km/s console.log(dz1.Oncar); var dz2 =new dazhong('小红',40); dz2.name = '高尔夫'; dz2.Oncar.push('小红'); dz2.drive();//司机小红驾驶着高尔夫,在高速公路上 dz2.chesu();//车速是40km/s console.log(dz2.Oncar);//["小明", "小bai", "小红"] console.log(dz1 instanceof car);//true console.log(dz1 instanceof dazhong);//true console.log(dz2 instanceof car);//true console.log(dz2 instanceof dazhong);//true
组合继承的方式是现在js里常用的继承方式。
4.原型式继承
function object(o){ function F(){ } F.prototype = o; return new F(); }在object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。从本质上讲,object()对传入其中的对象执行了一次浅复制。 ——《js高程》
在高程里面讲的原型式的继承就是这个原理。而ES5新增了 Object.create() 方法 规范化了原型式继承。
var car ={
name:"车",
Oncar:['小明','小白'],
driver:'人',
drive: function() {
console.log('司机'+this.driver+'驾驶着'+this.name+',在高速公路上');
}
}
var dz1 = Object.create(car);//继承
dz1.name = '斯柯达';
dz1.driver = '小明';
dz1.V = 200;
dz1.Oncar.push('小黑');
dz1.chesu = function() {
console.log('车速为'+this.V+'km/h');
}
dz1.drive();//司机小明驾驶着斯柯达,在高速公路上
dz1.chesu();//车速为200km/h
console.log(dz1.Oncar);//["小明", "小白", "小黑"]
var dz2 = Object.create(car);
dz2.name = '高尔夫';
dz2.driver = '小红';
dz2.V = 40;
dz2.Oncar.push('小白');
dz2.chesu = function() {
console.log('车速为'+this.V+'m/s');
}
dz2.drive();//司机小红驾驶着高尔夫,在高速公路上
dz2.chesu();//车速为40km/h
console.log(dz2.Oncar);//["小明", "小白", "小黑", "小白"]
和原型链的方式一样,原型式继承依然存在 引用类型变量共享的问题。
5.寄生式继承
寄生式继承是与原型式继承紧密相关的一种思路,创建一个仅用于封装继承过程的函数,该函数在内部用某种方式来增强对象,最后再像其他真地是它做了所有工作一样返回对象
function object(oo){ function F(){} F.prototype = oo; return new F(); } function jicheng(o) { var clone = object(o); clone.drive =function() { console.log('司机'+this.driver+'驾驶着'+this.name+',在高速公路上'); //强化... }; return clone; } var car ={ name:"车", Oncar:['小明','小白'], driver:'人', } var dz1 = jicheng(car);//继承 dz1.name = '斯柯达'; dz1.driver = '小明'; dz1.V = 200; dz1.Oncar.push('小黑'); dz1.chesu = function() { console.log('车速为'+this.V+'km/h'); } dz1.drive();//司机小明驾驶着斯柯达,在高速公路上 dz1.chesu();//车速为200km/h console.log(dz1.Oncar);//["小明", "小白", "小黑"]
可是我觉得...这个方法好像和原型式继承差不多(没啥用)....希望大佬们能给出这个方法存在的意义...
6.寄生组合式继承
据说是很棒的继承方式,就是写法比较复杂...寄生组合继承是对组合继承的改进.为什么要对组合继承进行改进呢?
因为在组合继承里面,无论什么情况下,都会调用两次超类构造函数。
function a(name) { this.name = name; this.type = [1,2,3]; } a.prototype.sayName = function() { console.log(this.name); }; function b(name,age){ a.call(this,name);//第二次调用a() this.age = age; } b.prototype = new a();//第一次调用a() b.prototype.constructor = b; b.prototype.sayAge =function(){ console.log(this.age); }
注释部分,第一次调用a构造函数的时候 b.prototype 会得到两个属性:name 和 type,他们位于b的原型里面。
第二次调用a的时候 则是在新对象上面创建了属性name 和type。新实例上面的name 和 type 屏蔽了原型中的name 和 type。因此就引出了 寄生组合式继承
其基本思想是 通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
function object(o){ function F(){}; F.prototype = o; return new F(); } function jsInherit(zi,chao){ var proto = object(chao.prototype);//创建对象 proto.constructor = zi;//增强对象 zi.prototype = proto;//指定对象 } function car(driver) { this.name = name || '车'; this.Oncar=['小明','小bai'];//车上的人 this.driver = driver; } car.prototype.drive = function() { console.log('司机'+this.driver+'驾驶着'+this.name+',在高速公路上'); }; function dazhong(driver,v) { car.call(this,driver);//继承 this.V = v || 0; } jsInherit(dazhong,car); dazhong.prototype.chesu = function () { console.log('车速是'+this.V+'km/h'); } var a = new dazhong('大白',69); a.name = 'suv'; a.drive(); a.chesu(); a.Oncar.push('大白');
以上就是我根据 js高程 里的内容结合自己的实践谈的js的继承,如果有什么地方不合理的希望大佬们指出来。
结合着这篇文章来看继承,会有更多的收获!
xiexiedajia