继承
关于继承简单的理解就是在子类中想要使用父类的属性和方法,那么就需要子类来继承父类的属性和方法。
- 那就涉及到构造函数,因为构造函数可以创建一堆有属性有方法合理的对象,书写 构造函数的时候属性直接书写在构造函数体内,方法书写在构造函数的原型(prototype)上,为了给所有的实例使用,而且创建出来的每一个实例都可以使用。
了解完继承的大致思维后,就需要去实现它,那么就出现了很多思路,又因为javascript的灵活性,所以就出现了很多的继承方法,在这里简单介绍几个,还有es6中继承的使用。
原型继承
- 思想:利用修改原型链的方式来达到继承的效果
原型集成的优点和缺点- 优点: 属性和方法都继承下来的
- 缺点:
- 在做 Student 的实例, 一个构造函数需要在两个位置传递参数
- 我继承来的属性, 不在自己身上
function Person(name) {
this.name = name
}
Person.prototype.sayHi = function () { console.log('Person') }
- Person 的实例是什么样子的
{
name: 'xxx',
__proto__: Person.prototype {
constructor: Person,
sayHi: function () {},
__proto__: Object.prototype
}
}
- 如果我想让 s1 在现在的基础上添加一个 name 属性和 sayHi 方法
- 在 s1 的 proto 里面只要加上 name 属性和 sayHi 方法
- 因为当你访问 s1 的成员的时候, 如果自己没有, 会自动去 proto 上查找
- 如果 proto 上没有, 再去 proto 上找
- s1 的 proto 是 Student.prototype
- 我只要修改了 Student.prototype 那么就可以实现了
借用构造函数继承 / call 继承 / 借用继承
- 利用父类构造函数体 和 call 方法来达到继承效果
- 构造函数也是一个函数
=> 可以不和 new 关键字连用
=> this 不是指向当前实例
=> 只要是执行的时候, this 指向谁, 就是像谁身上添加成员 - call 是一个可以在函数后面调用的方法
=> 所有函数都可以调用
=> 构造函数也是函数, 构造函数也可以调用 call 方法
=> 用来改变函数内部的 this 指向
=> 第二个参数开始一次给函数内传递参数 - 可以把父类当做普通函数使用
=> 通过 call 方法, 把父类函数内的 this 指向子类的实例
=> 父类里面添加的属性就是添加在子类的实例身上了
- 构造函数也是一个函数
借用继承的优点和缺点
- 优点:
- new 一个实例的时候, 在一个位置传递参数
- 继承来的属性出现在自己的身上
- 缺点:
- 父类 prototype 身上的内容不能继承下来
//父类
function Person(name) {
this.name = name
}
Person.prototype.sayHi = function () { console.log('Person') }
// 利用 call 方法来调用一下构造函数
var obj = {
a: 100
}
// 本次调用 Person 函数, 就是把函数内部的 this 改成了 obj
// 'jack' 就是传递给 name 形参的数据
Person.call(obj,'Jack')
console.log(obj)
组合继承
- 字面理解即可指 原型继承 和 借用继承 组合在一起的继承方案
- 同时使用 借用继承 和 原型继承
缺点:- 属性重复了
- 方法太远了
//父类
function Person(name) {
this.name = name
}
Person.prototype.sayHi = function () { console.log('Person') }
function Student(age, name) {
this.age = age
// 使用借用继承
Person.call(this, name)
}
// 使用原型继承继承方法
Student.prototype = new Person()
const s1 = new Student(15, "Jack")
console.log(s1)
拷贝继承 / for in 继承
- 利用 for in 循环遍历父类的实例来达到继承效果
- 父类的实例是存在属性和方法的
=> 就可以使用 forin 循环进行遍历
=> 在遍历的过程中, 不管自己本身的属性会被遍历出来
=> 原型上的方法也会被遍历出来 - 子类的原型本身是一个对象
=> 我可以给他赋值一个新的对象
=> 把父类实例里面的属性和方法全部都复制一份上去
- 父类的实例是存在属性和方法的
function Student(age) {
this.age = age
}
Student.prototype = {}
// 把父类实例里面的所有内容遍历出来, 直接放在子类的 原型上
const p = new Person('Jack')
for (let key in p) {
Student.prototype[key] = p[key]
}
const s1 = new Student(15)
console.log(s1)
寄生式继承
- 因为他是以 父类的实例 冒充 子类的实例所以叫寄生式继承
- 构造函数内部不要写 return
=> 当你 return 一个复杂数据类型的时候
=> 构造函数本身创建的对象就没有了, 你 return 什么就是什么
- 构造函数内部不要写 return
//父类
function Person(name) {
this.name = name
}
Person.prototype.sayHi = function () { console.log('Person') }
function Student(name) {
this.a = 100
Person.prototype.fn = function () {}
// 拿到父类的实例
return new Person(name)
}
// 看似你拿到的是 Student 的实例
// 因为你在构造函数体内书写了 return 复杂数据类型
// 所以你得到的本身的 Student 的实例不存在了
// 真实得到的内容就是 Perosn 的实例
const s1 = new Student('Jack')
console.log(s1)
寄生式组合继承
- 指 借用继承 和 拷贝继承 一起使用
- 并且在拷贝继承中, 指去拷贝父类的原型, 不拷贝父类的实例
=> 因为父类实例身上自己本身有的内容
=> 被借用继承的时候, 直接继承在了子类的身上
缺点:- for in 循环
- 当你给子类添加独立方法的时候, 和父类继承来的方法分不开
//父类
function Person(name) {
this.name = name
}
Person.prototype.sayHi = function () { console.log('Person') }
function Student(age, name) {
this.age = age
// 借用继承
// 只要书写在父类构造函数体内的内容都能继承下来
// 只是父类 prototype 上的内容继承不下来
Person.call(this, name)
}
// 利用拷贝继承的一部分, 来实现继承父类的原型
for (let key in Person.prototype) {
// 把父类原型上的所有内容添加到子类原型身上
Student.prototype[key] = Person.prototype[key]
}
Student.prototype.run = function () {
console.log('跑')
}
const s1 = new Student(15, 'Rose')
console.log(s1)
ES6 的类继承语法
- 在 ES6 语法标准里面, 有一个继承语法
- 为了实现 类的继承
语法:
class 子类 extends 你要继承的父类 {
constructor () {
// 为了继承父类的属性
super()
}
}
注意:
- 你在书写 super 的时候, 必须写在你自己的属性上面
- 先继承父类的属性, 再书写自己的属性
- 必须要书写 super
- ES6 的类语法可以继承 ES5 的构造函数
父类
class Person {
constructor (name) {
this.name = name
}
sayHi () {
console.log('Person')
}
}
// 子类
// 1. 在创建类的时候直接书写关键字进行继承
class Student extends Person {
constructor (age, name) {
// 相当于我们的 借用继承
// 2. 在 constructor 的最前面继承父类的属性
super(name)
this.age = age
}
run () {
console.log('跑')
}
}
const s1 = new Student(15, 'Jack')
console.log(s1)
类继承构造函数
//父类
function Person(name) {
this.name = name
}
Person.prototype.sayHi = function () { console.log('Person') }
// 子类继承自构造函数 Person
class Student extends Person {
constructor (age, name) {
super(name)
this.age = age
}
run () { console.log('跑') }
}
const s1 = new Student(16, 'Rose')
console.log(s1)
总结
用了这么多方法来实现继承,无非是想使用父类的方法,复制还是原型等方法都不算最佳的解决,甚至一些方法称不上继承,在es6类中得到了很好的解决,:继承的好处:
- a:提高了代码的复用性
- b:提高了代码的维护性
- c:让类与类之间产生了关系,是多态的前提
- 但是在使用的时候,因为父类与子类之间存在强耦合关系,那么当修改父类时,子类会受到影响,当你仅仅为了一个或者二个子类来写继承也完全没有必要,如果有很多子类时,那么父类的属性和方法一旦修改就会导致子类产生影响,所以在写的时候要考虑周全。