在ES6出现之前,一般是使用构造函数来实现批量的创建对象,而谈到对象,这就会想到面向对象式编程,面向对象有三大特征:封装、继承、多态,在js中主要以实现继承来体现出面向对象的思维方式。
目录
以下整理出了js常见的6种继承方式
1.原型链继承
原型链继承是通过原型对象可以被覆盖的特点,直接用父类的实例对象覆盖子类的原型对象实现的。
//父类
function Father(name) {
this.name = name;
this.eats = ['香蕉']
this.sayName = function () {
console.log(this.name);
}
}
Father.prototype.age = '10'
//子类
function Son(name) {
this.name = name
this.sonname = 'jack'
}
var father = new Father('mk')
Son.prototype = new Father()
var son1 = new Son('son')
var son2 = new Son()
son1.sayName();
console.log('son1', son1);
son1.eats.push('苹果')
console.log('son2', son2);
可以看出两个子实例都是吧父类的实例当做了自己的原型对象,实现了继承,但是我们向son1中的eats属性中push了一个‘苹果’后,输出son2的时候,它的原型里也有了一个‘苹果‘,所以存在一个弊端。以下是对原型链继承的总结:
重点:让子实例对象对象指向父实例对象
特点:子实例对象可继承的属性有:
缺点:1.所有子实例化对象共用一个原型对象,如果有一个发生改变则全部发生改变
2.子实例对象无法向父实例对象传参
3.父类所有的引用数据(对象,数组)会被子类共享,更改一个子类的数据,其他子类数据会发生改变
2.利用构造函数继承 (借助call方法)
构造函数是通过在子类中使用call方法改变父类的this指向,将父类的this指向子类,实现继承。
function Parent1(name) {
this.eats = ['苹果']
this.name = name
}
Parent1.prototype.getName = function () {
return this.name
}
function Child1(name) {
Parent1.call(this, name) //将Parent1的this指向Child1
this.type = 'child1'
}
var child = new Child1('qwe')
child.eats.push('香蕉')
var child1 = new Child1('abc')
console.log(child.name);
console.log(child);
console.log(child1);
child.getName()
可以看出,这种继承方式没有改变子类原型,而是在实例化的时候将父类的成员直接放在子类的实例成员上。在child中eats添加了一个‘香蕉’后,child1中的eats属性未受到影响。
不过由于没有改变子类原型,父类原型中的成员子类就无法获取、调用。产生报错
特点:
1.只继承父类构造函数的成员,没有继承父类原型的成员
2.解决了原型链继承的缺点
3.可以继承多个构造函数的属性(call多个)
4.子实例可以向父实例传参
优点:
父类的引用类型的数据不会被子类共享,不会互相影响
缺点:
1.只继承父类构造函数的成员,没有继承父类原型的成员,子类无法访(Parent1.prototype)
2.无法实现构造函数的复用(每次用,每次都要改变父构造函数的this指向)
3.每个新实例都有父类构造函数的副本,造成内存臃肿
3.组合式继承(将前两种结合)
这种方式结合了前两种继承方式的优缺点,结合起来的继承,代码如下
function Father(name) {
this.name = name
this.abc=123
}
Father.prototype.aaa = function (aaa) {
console.log(aaa);
}
function Son(name) {
Father.call(this, name) //构造函数继承
this.type = 'son'
}
Son.prototype = new Father() //原型链继承
var s = new Son('jack')
console.log(s);
s.aaa('aaa的参数')
优点是将前两种结合,子实例既有父类的属性和方法,而且可以调用父类原型中的方法。
缺点也显而易见:在子实例中,有两份一样的属性 abc 和 name,对性能有一定的影响。
优点:
1.父类可以复用
2.父类构造函数中的引用属性数据不会被勾选
缺点:
会调用两次父类的构造函数,会有两份一样的属性和方法,会影响性能
4.原型式继承
在 ES5 里面有一个 Object.create 方法,这个方法接收两个参数:一是用作新对象原型的对象、二是为新对象定义额外属性的对象(可选参数)。
var person = {
name: 'person',
eats: ['苹果'],
getName: function () {
return this.name
}
}
var person1 = Object.create(person) //将peeson作为person1的原型
person1.name = 'Jack'
person1.eats.push('香蕉')
person1.__proto__.getEats = function () {
return this.eats
}
console.log(person1);
console.log(person1.getEats());
var person2 = Object.create(person)
person2.eats.push('梨子')
console.log(person1.name === person1.getName());
console.log(person2.name);
console.log(person1.eats);
console.log(person2.eats);
通过 Object.create 这个方法可以实现普通对象的继承,不仅仅能继承属性,同样也可以继承方法
getName()。
可以看出,当我们在实例中对父级引用数据中添加了了值后,其他的实例中也出现了改变,这种继承方式的缺点也很明显了
缺点:多个实例的引用类型属性指向相同的内存,更改一个子类的数据,其他子类数据会发生改变
5.寄生式继承
使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法,这样的继承方式就叫作寄生式继承。
虽然其优缺点和原型式继承一样,但是对于普通对象的继承方式来说,寄生式继承相比于原型式继承,可以在父类的基础上添加更多的方法。
var person = {
name: 'person',
eats: ['苹果'],
getName: function () {
return this.name
}
}
function clone(obj) { //利用函数对继承后的实例添加方法
var clone = Object.create(obj)
clone.getEats = function () {
return this.eats
}
return clone
}
var person1 = clone(person)
console.log(person1.getName());
var person2 = clone(person)
console.log(person1);
person2.eats.push('香蕉')
console.log(person1.getEats());
console.log(person2.getEats());
我们可以看到 person1 是通过寄生式继承生成的实例,它不仅仅有 getName 的方法,而且可以看到它最后也拥有了 getEats 的方法。
从最后的输出结果中可以看到,person1 通过 clone 的方法,增加了 getEats 的方法,从而使 person1 这个普通对象在继承过程中又增加了一个方法,这样的继承方式就是寄生式继承。
优点:在继承的过程中可以添加一些方法。
缺点:
1.在上面第三种组合继承方式中提到了一些弊端,即两次调用父类的构造函数造成浪费
2.与原型式继承一致:多个实例的引用类型属性指向相同的内存,更改一个子类的数据,其他子类数据会发生改变
6.寄生组合式继承
目前最优的继承方案,在前面这几种继承方式的优缺点基础上进行改造,得出了寄生组合式的继承方式,也是所有继承方式里面相对最优的继承方式
function Father(name) {
this.name = name
this.abc = 123
this.eats = ['苹果']
}
Father.prototype.aaa = function (aaa) {
console.log(aaa);
}
function Son(name) {
Father.call(this, name) //构造函数继承
this.type = 'son'
}
var Fn = function () { }
Fn.prototype = Father.prototype //将父类原型赋值给Fn
Son.prototype = new Fn() //改变为Fn的实例
var s = new Son('jack')
var s1 = new Son('mk')
s1.eats.push('香蕉')
console.log(s);
console.log(s1);
s.aaa('aaa的参数')
可以看出来,这种寄生组合式继承方式,基本可以解决前几种继承方式的缺点,较好地实现了继承想要的结果,同时也减少了构造次数,减少了性能的开销
整体看下来,这六种继承方式中,寄生组合式继承是这六种里面最优的继承方式。