- 继承就是子类可以使用父类的所有功能,并且对这些功能进行扩展。
- JS继承机制主要为原型链继承、构造函数继承、组合继承、寄生继承、寄生组合继承、原型式继承和混合式继承。关于es6提出的class继承本文不做阐述,会另做一篇文章进行分析总结。
- 本文主要是做总结,各个继承方式的内容不做特别详细的说明。
原型链继承
继承规则
Child.prototype = new Parent() //将子类的原型指向父类的实例
代码实例
//定义父类,并在父类中定义属性与方法
function Parent() {
this.name = 'zs'
this.age = 18
this.arr = [1, 2]
this.sing = function () {
console.log(this.name);
}
}
//通过原型方式给父类添加属性与方法
Parent.prototype.sex = 'male'
Parent.prototype.song = function () {
console.log(this.sex);
}
//定义子类,在子类中定义相同的属性和方法,在原型链继承后可以覆盖父类的属性与方法。
function Child(name) {
this.name = name
}
//进行原型链继承
Child.prototype = new Parent()
Child.prototype.num = 15
//将子类实例化
let zdh = new Child('zdh')
Child.prototype.nums = 15
zdh.sing() //'zdh'
console.log(zdh.name); //'zdh'
zdh.song() //male
console.log(zdh.arr);//[1, 2]
zdh.arr.push(5)
console.log(zdh.arr);//[1, 2 , 5]
console.log(zdh.num);//undefined
console.log(zdh.nums);//15
let zxy = new Child('zxy')
console.log(zxy.name);//zxy
console.log(zxy.arr);//[1, 2 , 5]
优点
- 继承了父类的模板,又继承了父类的实例
缺点
- 无法实现多重继承
- 创建子类时,无法向父类传参
- 来自原型对象的所有属性都被共享了,这样如果不小心修改了原型对象中的引用类型属性,那么所有子类创建的实例对象都会受到影响
- 如果要给子类的原型上新增属性和方法,就必须放在
Child.prototype = new Parent()
这样的语句后面
构造函数继承
继承规则
在子类构造函数内部使用`call或apply`来调用父类构造函数
代码实例
function Parent() {
this.name = 'zs'
this.age = 18
this.arr = [1, 2]
this.sing = function () {
console.log(this.name);
}
}
Parent.prototype.sex = 'male'
Parent.prototype.song = function () {
console.log(this.sex);
}
function Child(name) {
this.name = name
Parent.call(this, ...arguments)//如果有同名的属性与方法会进行覆盖
}
//等同于
// function Child(name) {
// this.name = name
// this.name = 'zs'
// this.age = 18
// this.arr = [1, 2]
// this.sing = function () {
// console.log(this.name);
// }
// }
let zdh = new Child('zdh')
zdh.sing() //zs
console.log(zdh.name);//zs
zdh.song() //报错
console.log(zdh.arr);//[1, 2]
zdh.arr.push(5)
console.log(zdh.arr);//[1, 2 , 5]
let zxy = new Child('zxy')
console.log(zxy.name);//zs
console.log(zxy.arr);//[1, 2]
优点
- 解决了原型链继承中子类实例共享父类引用对象的问题,实现多继承,创建子类实例时,可以向父类传递参数
缺点
- 只能继承父类实例上的属性与方法,无法继承父类原型上的属性与方法。
- 实例并不是父类的实例,只是子类的实例(子类相对于深拷贝了父类实例上的属性与方法)
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
(无法实现函数复用’父类构造函数中的某个函数可能只是一个功能型的函数,它不论被复制了多少份,输出的结果或者功能都是一样的,那么这类函数是完全可以拿来复用的。但是现在用了构造函数继承,由于它是复制了父类构造函数中的属性和方法,这样产生的每个子类实例中都会有一份自己各自的方法,可是有的方法完全没有必要复制,可以用来共用的,所以就说不能够「函数复用」。)
组合继承
继承规则
function Parent() {
...
}
function Child() {
Parent.call(this, ...arguments)//构造函数继承
}
Child.prototype = new Parent() //原型链继承
代码实例
function Parent() {
this.name = 'zs'
this.age = 18
this.arr = [1, 2]
this.sing = function () {
console.log(this.name);
}
}
Parent.prototype.sex = 'male'
Parent.prototype.song = function () {
console.log(this.sex);
}
function Child(name) {
this.name = name
Parent.call(this, ...arguments)//如果有同名的属性与方法会进行覆盖
}
Child.prototype = new Parent()
let zdh = new Child('zdh')
zdh.sing() //zs
console.log(zdh.name);//zs
zdh.song() //male
console.log(zdh.arr);//[1, 2]
zdh.arr.push(5)
console.log(zdh.arr);//[1, 2 , 5]
let zxy = new Child('zxy')
console.log(zxy.name);//zs
console.log(zxy.arr);//[1, 2]
优点
- 可以继承父类实例属性和方法,也能够继承父类原型属性和方法
- 弥补了原型链继承中引用属性共享的问题
- 可传参,可复用
缺点
- 使用组合继承时,父类构造函数会被调用两次。
- 并且生成了两个实例,子类实例中的属性和方法会覆盖子类原型(父类实例)上的属性和方法,所以增加了不必要的内存。
寄生式组合继承
继承规则
function Parent() {
...
}
function Child() {
Parent.call(this, ...arguments)//构造函数继承
}
Child.prototype = Object.create(Parent.prototype)
代码实例
function Parent() {
this.name = 'zs'
this.age = 18
this.arr = [1, 2]
this.sing = function () {
console.log(this.name);
}
}
Parent.prototype.sex = 'male'
Parent.prototype.song = function () {
console.log(this.sex);
}
function Child(name) {
this.name = name
Parent.call(this, ...arguments)//如果有同名的属性与方法会进行覆盖
}
//Child.prototype = new Parent()
Child.prototype = Object.create(Parent.prototype)
let zdh = new Child('zdh')
zdh.sing() //zs
console.log(zdh.name);//zs
zdh.song() //male
console.log(zdh.arr);//[1, 2]
zdh.arr.push(5)
console.log(zdh.arr);//[1, 2 , 5]
let zxy = new Child('zxy')
console.log(zxy.name);//zs
console.log(zxy.arr);//[1, 2]
优点
寄生组合继承算是ES6
之前一种比较完美的继承方式吧。
它避免了组合继承中调用两次父类构造函数,初始化两次实例属性的缺点。
所以它拥有了上述所有继承方式的优点:
- 只调用了一次父类构造函数,只创建了一份父类属性
- 子类可以用到父类原型链上的属性和方法
- 能够正常的使用
instanceOf
和isPrototypeOf
方法
原型式继承
继承规则
//原型式继承的原理是创建一个构造函数,构造函数的原型指向对象,然后调用 new 操作符创建实例,并返回这个实例,本质是一个浅拷贝。
function objcet (obj) {
function F () {};
F.prototype = obj;
F.prototype.constructor = F;
return new F();
}
代码实例
function objcet (obj) {
function F () {};
F.prototype = obj;
F.prototype.constructor = F;
return new F();
}
var cat = {
heart: '❤️',
colors: ['white', 'black']
}
var guaiguai = create(cat)
var huaihuai = create(cat)
console.log(guaiguai)
console.log(huaihuai)
console.log(guaiguai.heart)
console.log(huaihuai.colors)
优点
- 在不用创建构造函数的情况下,实现了原型链继承,代码量减少一部分。
缺点
- 一些引用数据操作的时候会出问题,两个实例会公用继承实例的引用数据类
- 谨慎定义方法,以免定义方法也继承对象原型的方法重名
- 无法直接给父级构造函数使用参数
寄生式继承
继承规则
//寄生式继承也没啥东西的,它就是在原型式继承的基础上再封装一层,来增强对象,之后将这个对象返回。
function createAnother (original) {
var clone = Object.create(original);; // 通过调用 Object.create() 函数创建一个新对象
clone.fn = function () {}; // 以某种方式来增强对象
return clone; // 返回这个对象
}
代码实例
var cat = {
heart: '❤️',
colors: ['white', 'black']
}
function createAnother (original) {
var clone = Object.create(original);
clone.actingCute = function () {
console.log('我是一只会卖萌的猫咪')
}
return clone;
}
var guaiguai = createAnother(cat)
var huaihuai = Object.create(cat)
guaiguai.actingCute()
console.log(guaiguai.heart)
console.log(huaihuai.colors)
console.log(guaiguai)
console.log(huaihuai)
优点
- 再不用创建构造函数的情况下,实现了原型链继承,代码量减少一部分。
缺点
- 一些引用数据操作的时候会出问题,两个实例会公用继承实例的引用数据类
- 谨慎定义方法,以免定义方法也继承对象原型的方法重名
- 无法直接给父级构造函数使用参数
混合式继承
继承规则
//这个**混入方式继承**其实很好玩,之前我们一直都是以一个子类继承一个父类,而**混入方式继承**就是教我们如何一个子类继承多个父类的。
//在这边,我们需要用到`ES6`中的方法`Object.assign()`。
//它的作用就是可以把多个对象的属性和方法拷贝到目标对象中,若是存在同名属性的话,后面的会覆盖前面。(当然,这种拷贝是一种浅拷贝啦)
function Child () {
Parent.call(this)
OtherParent.call(this)
}
Child.prototype = Object.create(Parent.prototype)
Object.assign(Child.prototype, OtherParent.prototype)
Child.prototype.constructor = Child
代码实例
function Parent(sex) {
this.sex = sex
}
Parent.prototype.getSex = function () {
console.log(this.sex)
}
function OtherParent(colors) {
this.colors = colors
}
OtherParent.prototype.getColors = function () {
console.log(this.colors)
}
function Child(sex, colors) {
Parent.call(this, sex)
OtherParent.call(this, colors) // 新增的父类
this.name = 'child'
}
Child.prototype = Object.create(Parent.prototype)
Object.assign(Child.prototype, OtherParent.prototype) // 新增的父类原型对象
Child.prototype.constructor = Child
var child1 = new Child('boy', ['white'])
child1.getSex() //boy
child1.getColors() //[ 'white' ]
console.log(child1)//Child { sex: 'boy', colors: [ 'white' ], name: 'child' }
引用