js继承
又到了双休,但是手机老是给我提示暴雨预警,哪都不敢去。所以借着这个时机来复习一下js基础中的继承,顺便总结一下中间踩到的坑,每个实例的代码都全部写下来,虽然有很多是重复的但是这样方便查看和理解,如有错误欢迎指点^_^
原型链继承
原型链继承,就是让子类的原型属性指向父类的实例,这样子类没有在自己的实例里找到属性就会去原型上找(此时是父类的实例),再没有找到就去父类原型上找
function Super() { this.supArr = ['a', 'b'] this.name = 'super' }
Super.prototype.sayName = function() { console.log(this.name) }
function Sub() { }
Sub.prototype.saySubName1 = function() { console.log('saySubName1') }
Sub.prototype = new Super()
Sub.prototype.saySubName2 = function() { console.log('saySubName2') } Sub.prototype.sayArr = function() { console.log(this.supArr) }
let sub1 = new Sub() let sub2 = new Sub()
sub1.name ='aaa' sub2.name = 'bbb' sub1.sayName() sub2.sayName() sub1.supArr.push('c') sub2.sayArr()
|
使用原型链继承,可以帮助我们更加深刻的理解原型,但是也有很多缺点
缺点,1.修改父类引用类型,所有子类实例都会受到影响
2.不能实现多继承 3.不能向父类构造函数传参
构造函数继承
构造函数继承就是在子类的构造函数中通过call或apply方法,调用父类构造函数
function Super(name) { this.name = name this.supArr = ['a', 'b'] }
function Sub(name) { Super.call(this, name) } Sub.prototype.sayName = function() { console.log(this.name) } Sub.prototype.sayArr = function() { console.log(this.supArr) } let sub1 = new Sub('zzj') let sub2 = new Sub('sjj') sub1.sayName() sub2.sayName()
sub1.supArr.push('c')
sub2.sayArr()
|
在这里本能的去父类原型上写方法,但是在调用的时候并获取不到父类的方法,
这是因为在子类通过call调用父类构造函数时,只是拷贝了父类中实例的属性
优点:1.可以向父类构造函数传参, 2. 引用类型不会受到其他实例的影响
缺点:1. 定义的方法不能复用,每个实例都会重新定义原型上的方法
组合继承
组合继承是结合原型链继承和构造函数继承,弥补了原型链继承不能传参和引用类型的改变会影响到其他实例,也弥补了构造函数继承只能在子类中定义方法,每个实例都会重新定义一次方法的缺点。
function Super(name) { this.supArr = ['a', 'b'] this.name = name }
Super.prototype.sayName = function() { console.log(this.name) }
function Sub(name) { Super.call(this, name) }
Sub.prototype = new Super() Sub.prototype.sayArr = function() { console.log(this.supArr) }
let sub1 = new Sub('zzj') let sub2 = new Sub('sjj') sub1.sayName() sub2.sayName()
sub1.supArr.push('c')
sub2.sayArr()
|
虽然弥补了缺陷,但是子类调用构造函数时通过call拷贝了父类上的实例属性,然后子类又通过原型指向了父类的实例,那么子类原型上又有了父类上的实例属性。
优点: 1.结合了 原型链继承 和构造函数继承,弥补了各自的缺陷
缺点: 调用了两次父类构造函数,导致子类实例和原型上都有父类实例上的属性
原型式继承
原型式继承,就是基于已有对象,创建一个新对象和Object.create类似
function Object(sup) { function Fn() {} Fn.prototype = sup return new Fn() }
let person = { name: 'super', arr: ['a', 'b'], sayName() { console.log(this.name) }, sayArr() { console.log(this.arr) } }; let newObj = Object(person) let newObj2 = Object(person) newObj.name = 'newObj' newObj2.name = 'newObj2' newObj2.sayName() newObj.sayName()
newObj.arr.push('c') newObj2.sayArr()
|
缺点和原型链继承一样
寄生组合继承
结合上面三个,将子类的原型指向通过Object函数创建的中间函数的原型,因为中间函数没有实例属性,这样子类原型上就不会有父类属性,这句话看代码就理解啦。中间我产生了一个疑问为什么不直接让子类原型指向父类原型呢?
function ObjectCreate(sup) { function Fn() {} Fn.prototype = sup.prototype; return new Fn(); }
function inherit(sub, sup) { var prototype = ObjectCreate(sup); prototype.constructor = sub; sub.prototype = prototype; }
function Super(name) { this.supArr = ['a', 'b'] this.name = name }
Super.prototype.sayName = function() { console.log(this.name) }
function Sub(name) { Super.call(this, name) }
inherit(Sub, Super)
Sub.prototype.sayArr = function() { console.log(this.supArr) }
let sub1 = new Sub('zzj') let sub2 = new Sub('sjj')
sub1.sayName() sub2.sayName()
sub1.supArr.push('c')
sub1.sayArr() sub2.sayArr()
|
优点: 完美
缺点: 踩的坑有点多, 1.在Object函数中 应该是将父类原型赋值给 中间函数,而不是父类构造函数, 2. 定义子类原型方法应该在寄生组合操作之后
掌握到这些,那js的继承应该就差不多了,不过现在我更喜欢ES6的class中extend继承,但是这些理解了,有木有感觉自己又变强了^_^