概念
为什么继承:
1. 为了把公共的内容提取出来变成更加公共的内容
2. 为了让所有的类都能使用
含义:
当 A 构造函数的实例, 能够使用 B 构造函数的 属性(构造函数体内)和方法(构造函数原型)
则称 A 构造函数(类) 继承自 构造函数B(类)
A 构造函数(类) 叫做 子类
B 构造函数(类) 叫做 父类
原型继承
说到底,JS其实是一门介于面向对象和面向过程之间的编程语言。在JS发展初期,为实现面向对象编程的特点(继承),于是网络中的各路大神纷纷开始JS继承的试炼之路。
JS发展最红火的那几年,第一种较为流行的继承方式就是原型继承
核心:将子类的prototype指向父类实例化对象
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }
// 子类
function Student(gender) {
this.gender = gender
}
// 实现继承
Student.prototype = new Person('Rose', 18)
const s = new Student('男')
console.log(s)
优点:子类能够继承父类的所有属性和方法
缺点:继承自父类的属性不可修改,且由于子类的prototype指向了父类实例对象,导致子类没有了自己的原型(prototype),即子类不能有自己的私有方法
借用继承
有了前者的原型继承,在其基础上,为了弥补其缺点,又出现了另一种继承方式——借用继承
核心:利用call
或者applay
强行改变父类的this指向
借用继承中已经完美继承到了父类的构造函数体内的属性,但是无法继承到父类原型上的方法
// 父类
function Person(name, age) {
this.name = name
this.age = age
// this.sayBey = function(){console.log('bey bey');}
}
Person.prototype.sayHi = function () { console.log('hello world') }
// 子类
function Student(gender, name, age) {
//在子类中调用父类Person,将Person当做普通函数看待
//Student函数内调用Person函数---Person的this指向Window
//使用call,改变Person的this指向
//子类看做自定义构造函数---this指向new Student时的实例化对象
//Person函数内的this指向了当前实例化对象
//将Person函数内(仅仅写在函数内的)的所有成员,插入到Student实例
Person.call(this, name, age)
this.gender = gender
}
// 子类自己的原型方法
Student.prototype.study = function () { console.log('学习中...') }
const s = new Student('男', 'Jack', 18)
//子类中除了添加了父类函数中的内容,其他的什么也没有改变
console.log(s)
优点:子类可以有自己的原型prototype,且每一个从父类继承下来的属性都是自己的独立私有属性
缺点:子类不能继承父类原型(prototype)上的方法
组合继承
既然原型继承和借用继承都有其致命缺点,并且两者刚好互补,于是就出现了第三种继承——组合继承
核心:将 原型继承 和 借用继承 组合使用
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }
// 子类
function Student(gender, name, age) {
// 利用借用继承继承属性
Person.call(this, name, age)
this.gender = gender
}
// 利用原型继承继承方法(属性和方法都能继承)
Student.prototype = new Person()
const s = new Student('男', 'Jack', 18)
console.log(s)
优点:子类能够继承父类的属性(借用)和方法(原型),且继承的属性都是自己独立私有的
缺点:子类没有自己的原型(prototype),调用了两次父类构造函数,中间多套了一环,且多了一套属性
拷贝继承
补充:
in 关键字的作用
语法: ‘key’ in 对象
得到结果: 一个布尔值
true, 表示该 key 在该对象的原型链上(可能是自己的属性, 也可能是原型上的属性)
false, 表示该 key 在该对象的整个原型链上查找不到
hasOwnProperty()
语法: 对象.hasOwnProperty(‘key’)
返回值: 一个布尔值
true, 表示该 key 是该对象自己本身的属性
false, 表示该 key 不是该对象自己本身的属性
既然组合继承也行不通,另辟蹊径,直接将父类实例对象复制一份放在子类原型上不就行了——第四种继承 拷贝继承
核心:利用 for..in..
继承,利用in
关键字遍历—把父类实例的所有内容,遍历复制一份拷贝到子类的原型上
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }
// 子类
function Student(gender) {
this.gender = gender
}
Student.prototype.study = function () { console.log('学习中...') }
// 准备一个父类的实例
const p = new Person('Jack', 18)
// 遍历父类的实例
for (let k in p) {
Student.prototype[k] = p[k]
}
const s = new Student('男')
console.log(s)
优点:子类能够继承父类的属性和方法
缺点:使用了for..in..
遍历,遍历了整个原型链,性能极差,继承的父类属性一致,没有自己的私有属性
组合继承2.0
拷贝继承永远避不开的一个问题是使用了 for…in…遍历,这是最致命的地方,也是不能让圈内人满意的原因。
之后就有了 组合继承2.0版本
核心:结合 借用继承 和 拷贝继承(仅拷贝父类的prototype)的一部分 实现的继承方案
只要有拷贝继承,就避免不了for…in…遍历的问题。故组合继承2.0也被大家诟病。
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }
// 子类
function Student(gender, name, age) {
// 利用借用继承继承属性
Person.call(this, name, age)
this.gender = gender
}
Student.prototype.study = function () { console.log('学习中...') }
// 子类的原型继承父类的原型
// 利用 for in 循环只从 父类.prototype 开始遍历
for (let k in Person.prototype) {
Student.prototype[k] = Person.prototype[k]
}
const s = new Student('男', 'Jack', 18)
console.log(s)
优点:子类拥有自己的私有属性,能够继承父类的原型方法
缺点:使用了for..in..
遍历,遍历了整个原型链,性能极差
寄生继承的发展
后来出现的一种新颖的继承方式——寄生继承
唾弃版
该版本不能称之为寄生继承,但放在这里是因为这个名字是写这个版本的人首次提出来的。——最垃圾的一种继承,完全是文字游戏掩盖了继承的意义
直接在子类的构造函数体内返回父类的实例(这是正常人能提出来的?)
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }
// 子类
function Student(gender, name, age) {
const p = new Person(name, age)
return p
}
// 看似得到的是子类的实例
// 本质得到的就是一个父类的实例
const s = new Student('', 'Jack', 18)
console.log(s)
寄生继承
经过后人的努力,JS终于摸到了完美继承的门槛,使用第三方构造函数。
思想:利用第三个构造函数去寄生父类,然后利用子类去继承第三方的构造函数
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }
// 子类
function Student(gender) {
this.gender = gender
}
- 第一版
利用原型继承,让第三方构造函数继承父类,然后同样利用原型继承,让子类继承第三方构造函数
// 1. 出现一个第三方构造函数
function Third() {}
// 2. 利用原型继承, 让 Third 当做子类, Person 当做父类
Third.prototype = new Person('Jack', 18)
// 3. 利用原型继承, 让 Student 继承 Third
Student.prototype = new Third()
Student.prototype.study = function () { console.log('学习中...') }
- 第二版
改变了第三方继承父类的方式,使用复制的方法,让第三方的原型等于父类的原型
//1. 出现一个第三方构造函数
function Third() {}
// 2. 利用赋值操作, 让 Third 的 原型 等于 父类的原型
Third.prototype = Person.prototype
// 3. 利用原型继承, 让 Student 继承 Third
Student.prototype = new Third()
Student.prototype.study = function () { console.log('学习中...') }
寄生式组合继承(玉伯初版)
寄生式组合继承是国内的前端大神玉伯写出来的,牛*
核心思想:在子类中使用借用继承父类——拿到写在父类构造函数体内的属性和方法。然后使用赋值操作,让第三方的原型等于父类的原型,让子类通过原型继承的方式继承第三方构造函数——第三方构造函数上除了父类的原型外什么也没有,故使用原型继承拿到第三方构造函数上的protorype,即是继承了父类的prototype
比较组合继承而言,组合继承中多了一套无用的属性,且没有自己的私有方法,而寄生式组合继承完美的解决了这个问题
寄生式组合继承出版唯一的问题点—占用了一个变量名
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }
// 子类
function Student(gender, name, age) {
// 利用借用继承继承属性
Person.call(this, name, age)
this.gender = gender
}
// 把 寄生继承 和 借用继承 完美结合
function Third() {}
// 2. 利用赋值操作, 让 Third 的 原型 等于 父类的原型
Third.prototype = Person.prototype
// 3. 利用原型继承, 让 Student 继承 Third
Student.prototype = new Third()
//多写一步,将子类继承来的prototype写上其构造函数为Student---其实不是
Student.prototype.constructor = Student
Student.prototype.study = function () { console.log('学习中...') }
寄生式组合继承(玉伯完善版)
使用自执行函数将第三方构造函数包起来—解决了玉伯初版中多占用一个变量名的问题
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }
// 子类
function Student(gender, name, age) {
// 利用借用继承继承属性
Person.call(this, name, age)
this.gender = gender
}
// -----------------------------------------------
;(function () {
// 把 寄生继承 和 借用继承 完美结合
function Third() {}
// 2. 利用赋值操作, 让 Third 的 原型 等于 父类的原型
Third.prototype = Person.prototype
// 3. 利用原型继承, 让 Student 继承 Third
Student.prototype = new Third()
Student.prototype.constructor = Student
})()
// -----------------------------------------------
//将子类自己的操作写在自执行函数外面
Student.prototype.study = function () { console.log('学习中...') }
ES6中的类继承
后来官方出来了类继承,使用关键字extends。
使用extends
关键字
=> 语法: class 子类 extends 父类 {}
+ 需要用到 super 关键字(超类)
=> 需要在子类的 构造器(constructor) 内调用使用
=> 注意: 需要调用在 this.xxx 的前面
父类可以是构造函数,也可以是类,无所谓
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }
// 子类
// 创建一个继承自 Person 的 Student 类
class Student extends Person {
constructor (gender, name, age) {
// super 等价于原先的 借用继承
super(name, age)
this.gender = gender
}
study () { console.log('学习中...') }
}
const s = new Student('男', 'Jack', 18)
console.log(s)
其实官方底层extends函数使用的方法就是寄生式组合继承,奈何官方跟你玩赖。。。不承认