文章目录

  • ​​继承 :​​
  • ​​为什么需要继承 ?​​
  • ​​构造函数 :​​
  • ​​常见的继承方案 :​​
  • ​​1. 原型继承 : Student.prototype = new Person('Jack', 18)​​
  • ​​2. call 继承 : Person.call(this, name, age)​​
  • ​​扩展:用apply写​​
  • ​​3. 组合继承 :​​
  • ​​4. ES6 的继承语法​​
  • ​​总结:​​
  • ​​原型继承方法代码​​
  • ​​call继承方法代码​​
  • ​​组合继承方法代码​​
  • ​​ES6 的继承语法​​

继承 :

  • 继承是一个关于 构造函数 的高阶应用
  • 继承一定是出现在 两个构造函数 之间的关系
  • 什么是 继承 :
    => 当 构造函数 A 的 实例 , 使用了 构造函数 B 原型 上书写的属性和方法
    => 此时, 我们就说 A 和 B 出现了 继承关系
    => 构造函数 A 继承自 构造函数 B
    => 构造函数 A 是 构造函数 B 的 子类
    => 构造函数 B 是 构造函数 A 的 父类

为什么需要继承 ?

  • 我们面向对象开发的核心, 是 => 高内聚 低耦合 <=
  • 来思考 ? 进行假设 :
    => 我书写一个 苹果类 :
    ~> 颜色, 品牌, 重量, 水分, 名称
    ~> 吃的方法: 削皮吃, 去核吃
    => 我书写一个 橘子类 :
    ~> 颜色, 品牌, 重量, 水分, 名称
    ~> 吃的方法: 剥皮吃, 吐籽吃

可以看到 : 苹果类 和 橘子类 有 共同的 部分

  • 所以换一种书写方法 :
    => 水果类 :
    ~> 颜色, 品牌, 重量, 水分, 名称
    => 苹果类 :
    ~> 继承 水果类
    ~> 书写我独立的内容
    ~> 形状
    => 橘子类 :
    ~> 继承 水果类
    ~> 书写我独立的内容
    ~> 额外用途

构造函数 :

  • 构造函数 :
    => 原型 ( prototype ) :

每一个函数 天生自带一个属性, 叫做 prototype , 是一个 对象数据类型

=> 为什么需要原型 :

~> 为了解决 构造函数 的不合理

~> 书写 构造函数 的时候

~> 属性 直接书写在 构造函数体内

~> 方法 书写在 构造函数 的 原型 上

=> 目的: 为了书写一些方法, 给到该 构造函数 的 实例 使用

~> 把每一个实例都会用到的方法, 提取出来放在了 构造函数 的 原型 上

A 继承自 C , A 是 子类 , C 是 父类

B 继承自 C , B 是 子类 , C 是 父类

继承的目的 , 就是为了能够使用 构造函数 C 身上的 属性 和 方法 ,

把公共的东西提取出来成为更公共的东西

常见的继承方案 :

1. 原型继承 : Student.prototype = new Person(‘Jack’, 18)

原型继承,就是在本身的原型链上加一层结构

function Student() {}
Student.prototype = new Person('Jack',20)

原型继承(原型链继承) :

  • 核心: 让子类的 原型 指向 父类的 实例
  • 语法: ​​子类.prototype = new 父类( )​

优点:

  • 父类的 构造函数体内的 属性 和 原型 上的 方法 都可以继承下来

缺点:

  • 本该一个构造函数完成的所有参数, 需要分开两个地方传递
  • 同样都是给 子类实例 使用的属性, 在两个位置传递参数
  • 继承下来的属性不在自己身上, 子类的实例的所有属性分开了两部分书写
  • 访问的属性需要去到 ​​__proto__​​ 上才能找到
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }
const p = new Person('Jack', 18)
/*
父类的实例 :

p === {
name: 'Jack',
age: 18,
__proto__(Person.prototype): {
sayHi: function () {},
constructor: Person,
__proto__(Object.prototype): {
__proto__: null
}
}
}
不管是谁继承了 Person 类
+ 要求继承的构造函数的实例, 可以使用 name 和 age 属性
+ 要求继承的构造函数的实例, 可以使用 sayHi 方法
*/

[js] 继承 原型继承,call继承,组合继承,ES6继承_父类

[js] 继承 原型继承,call继承,组合继承,ES6继承_javascript_02

function Person(name, age) {
this.name = name
this.age = age
}

// 当 Person构造函数书写完毕以后
// 此时会伴生一个 Person.prototype 是一个对象数据类型
// 我就可以向 Person.prototype 上添加一个成员
// Person.prototype.a = 100
// 我也可可以把 Person.prototype 从新赋值

// 本身你的 Person.prototype 保存的是一个 对象数据类型 的地址
// 我给你赋值为一个新的 对象数据类型 的地址
// 就把你本身保存的地址覆盖了
const obj = {
message: '我是自定义的 obj 对象'
}
Person.prototype = obj

[js] 继承 原型继承,call继承,组合继承,ES6继承_子类_03

function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }




function Student(gender) {
this.gender = gender
}

const p = new Person('Jack', 18)
Student.prototype = p

[js] 继承 原型继承,call继承,组合继承,ES6继承_父类_04

// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }


// 子类
function Student(gender) {
this.gender = gender
}

const p = new Person('Jack', 18)

// Student 的实例可以使用 Person 书写的属性和方法了
// 我们就说 Student 继承自 Person
// Student 是 Person 的 子类
// Person 是 Student 的 父类
Student.prototype = p


// s 是 Student 的实例
// s 所属的构造函数是 Student
// s.__proto__ 指向 所属构造函数的 Prototype
// 因为 Student.prototype === p
// s.__proto__ === p
const s = new Student('男')
console.log(s)

[js] 继承 原型继承,call继承,组合继承,ES6继承_父类_05

[js] 继承 原型继承,call继承,组合继承,ES6继承_构造函数_06


根据原型链的规则, 对象访问

当你需要访问 s 的 gender 成员的时候,s 自己本身就有, 直接使用

console.log(s.gender) // 自己的, 男

[js] 继承 原型继承,call继承,组合继承,ES6继承_构造函数_07

当你需要访问 s​name​ 成员的时候
​​​s​​​ 自己本身没有, 需要去到 自己的 ​​__proto__​​​ 上查找,​​s​​​ 自己本身的 ​​__proto__​​​ 是 ​​p​​​, 所以就是在使用 ​​p​​​身上的​​name​​​成员,​​p​​​ 是 ​​Person​​​ 的实例,所以 ​​Student​​​ 的实例使用的是 ​​Person​​ 构造函数体内书写的属性

console.log(s.name)

[js] 继承 原型继承,call继承,组合继承,ES6继承_javascript_08

当你需要访问 s​sayHi​ 成员的时候

​s​​​ 自己本身没有, 需要去到 自己的 ​​__proto__​​​ 上查找
因为自己的 ​​​__proto__​​​(就是​​p​​​) 上也没有, 再去到 ​​p.__proto__​​​上查找,其实这时候就是去 ​​Person.prototype​​​上查找的内容,此时 ​​Student​​​ 的实例使用了 ​​Person.protptype​​ 上书写的 方法

s.sayHi()

[js] 继承 原型继承,call继承,组合继承,ES6继承_构造函数_09

总结:

// 因为这句代码的执行
// Student 的实例可以使用 Person 书写的属性和方法了
// 我们就说 Student 继承自 Person
// Student 是 Person 的 子类
// Person 是 Student 的 父类
Student.prototype = new Person('Jack', 18)

2. call 继承 : Person.call(this, name, age)

把父类构造函数体借用过来使用一下而已

function Student(name, age) {
Person.call(this, name, age)
}

call 继承 ( 借用构造函数继承 ) :

  • 核心: 把父类构造函数体, 当做普通函数调用,
    利用 call 方法调用父类构造函数 并 改变 this 指向
  • 语法: 在子类构造函数体内书写 ​​父类.call(this)​

优点:

  • 一个实例使用的属性可以在一个位置传递参数
  • 可以把继承来的属性直接出现在 子类的实例 身上

缺点:

  • 只能继承 父类构造函数体内 书写的内容, 父类构造函数 原型上 的内容不能继承(就是继承不了sayHi了)
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }



// 子类
function Student(gender, name, age) {
this.gender = gender

// 这里是 Student 的构造函数体内
// 这里的 this 是 Student 的每个实例对象
// 利用 call 调用 Person 构造函数, 把 Person 内的 this 修改为 Student 内的 this
// 把 Person 内的 this 修改为 Student 的每一个实例对象
// Person 构造函数体内书写成员就都添加到了 Student 的实例身上
Person.call(this, name, age)
}
Student.prototype.play = function () { console.log('你好 世界') }

const s = new Student('男', '张三', 20)
console.log(s)

从小例子开始分析:

​A.call(B,x,y)​​ 改变函数A的this指向,使之指向B;

function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }

// 既然 Person 可以当做普通函数调用
// call 方法就是用来调用函数, 并且改变 this 指向的
const obj = { message: '我是自定义的一个 obj 对象' }

// 在调用 Person 的时候
// 使用 call 方法, 改变Person的this指向,把 Person 内的 this 改成 obj
// Person 内书写的两个代码添加的成员, 加在 obj 身上了
Person.call(obj, 'Jack', 20)
console.log(obj)

obj身上就有name和age了

[js] 继承 原型继承,call继承,组合继承,ES6继承_父类_10

// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }

// 子类
function Student(gender, name, age) {
this.gender = gender
}
Student.prototype.play = function () { console.log('你好 世界') }

const s = new Student('男', '张三', 20)


// 因为 call 方法调用 Person 的时候, 把 Person 内的 this 修改为 s 了
// 所以 name 和 age 成员添加到了 s 身上
Person.call(s, 'Rose', 20)
console.log(s)

[js] 继承 原型继承,call继承,组合继承,ES6继承_构造函数_11

// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }


// 子类
function Student(gender, name, age) {
this.gender = gender

// 这里是 Student 的构造函数体内
// 这里的 this 是 Student 的每个实例对象
// 利用 call 调用 Person 构造函数, 把 Person 内的 this 修改为 Student 内的 this
// 把 Person 内的 this 修改为 Student 的每一个实例对象
// Person 构造函数体内书写成员就都添加到了 Student 的实例身上
Person.call(this, name, age)
}
Student.prototype.play = function () { console.log('你好 世界') }

const s = new Student('男', '张三', 20)
console.log(s)

[js] 继承 原型继承,call继承,组合继承,ES6继承_javascript_12

const s2 = new Student('女', '李四', 22)
console.log(s2)

[js] 继承 原型继承,call继承,组合继承,ES6继承_构造函数_13

扩展:用apply写

[js] 继承 原型继承,call继承,组合继承,ES6继承_子类_14

3. 组合继承 :

就是把 原型继承 和 借用构造函数继承 两个方式组合在一起

function Student() {
Person.call(this)
}
Student.prototype = new Person('Jack',20)
  • 核心: 把 原型继承 和 call 继承 合并在一起就做组合继承
  • 优点:
    => 父类构造函数体内的属性和原型上的方法都可以继承
    => 继承下来的属性在子类实例对象自己的身上
  • 缺点:
    => 原型上会多一套继承下来的属性名

原型继承 优缺点 :

[js] 继承 原型继承,call继承,组合继承,ES6继承_javascript_15

call继承 优缺点:

[js] 继承 原型继承,call继承,组合继承,ES6继承_父类_16


两种方法的优缺点刚好互补

// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }

// 子类
function Student(gender, name, age) {
this.gender = gender

// 实现 call 继承
// 目的: 为了把父类构造函数体内的内容放在子类实例自己身上
Person.call(this, name, age)
}

// 实现原型继承
// 目的: 为了把 Person 的 prototype 内的内容继承下来
Student.prototype = new Person('Jack',20)

Student.prototype.play = function () { console.log('你好 世界') }

// 创建子类的实例
const s = new Student('男', '张三', 20)
console.log(s)

[js] 继承 原型继承,call继承,组合继承,ES6继承_子类_17


​Student.prototype = new Person()​​可以不用传参数,因为Student自己有name,age,直接拿自己的了

// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }

// 子类
function Student(gender, name, age) {
this.gender = gender

// 实现 call 继承
// 目的: 为了把父类构造函数体内的内容放在子类实例自己身上
Person.call(this, name, age)
}

// 实现原型继承
// 目的: 为了把 Person 的 prototype 内的内容继承下来
Student.prototype = new Person()

Student.prototype.play = function () { console.log('你好 世界') }

// 创建子类的实例
const s = new Student('男', '张三', 20)
console.log(s)

[js] 继承 原型继承,call继承,组合继承,ES6继承_父类_18

4. ES6 的继承语法

下面表示创造一个 Student 类,继承自 Person 类

class Student extends Person {
constructor (name, age) {
// 必须在 constructor 里面执行一下 super() 完成继承
super(name, age)
}
}

ES6 的继承方案 :

  • ES6 官方提出了 关键字 来实现继承

ES6 的继承语法:

  • 语法分成两个部分 :
  1. 书写子类的时候, 使用 extends 关键字
    => ​​class 子类名 extends 父类 { ... }​
  2. 在子类的 constructor 内书写
    => ​​super( )​
  • 注意:

=> 必须要两个条件同时书写
=> 在 constructor 内书写 super 的时候, 必须写在所有 this 的 最前面
( 必须要先继承 : 你必须要继承传统 , 然后再创新 )

// ES6写法的父类
// class Person {
// constructor (name, age) {
// this.name = name
// this.age = age
// }

// sayHi () { console.log('hello world') }
// }


// ES5写法的父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }


// ES6 的类的继承
// 创建一个 继承自 Person 的 Student 类
// extends 关键字相当于原型继承
class Student extends Person {
constructor (gender, name, age) {
// 相当于在调用父类构造函数体, 把 name 和 age 传递过去
// 相当于 call 继承
super(name, age)
this.gender = gender
}

play () { console.log('你好 世界') }
}

const s = new Student('男', 'Jack', 20)
console.log(s)

[js] 继承 原型继承,call继承,组合继承,ES6继承_子类_19

总结:

原型继承方法代码

// 父类
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('Jack', 18)

const s = new Student('男')
console.log(s)

[js] 继承 原型继承,call继承,组合继承,ES6继承_子类_20

call继承方法代码

// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }


// 子类
function Student(gender, name, age) {
this.gender = gender

// call 继承核心代码
Person.call(this, name, age)
}

Student.prototype.play = function () { console.log('你好 世界') }

const s = new Student('男', '张三', 20)
console.log(s)

[js] 继承 原型继承,call继承,组合继承,ES6继承_构造函数_21

组合继承方法代码

// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }

// 子类
function Student(gender, name, age) {
this.gender = gender

// 实现 call 继承
// 目的: 为了把父类构造函数体内的内容放在子类实例自己身上
Person.call(this, name, age)
}

// 实现原型继承
// 目的: 为了把 Person 的 prototype 内的内容继承下来
Student.prototype = new Person()

Student.prototype.play = function () { console.log('你好 世界') }

// 创建子类的实例
const s = new Student('男', '张三', 20)
console.log(s)

[js] 继承 原型继承,call继承,组合继承,ES6继承_父类_22

ES6 的继承语法

// ES6写法的父类
// class Person {
// constructor (name, age) {
// this.name = name
// this.age = age
// }

// sayHi () { console.log('hello world') }
// }


// ES5写法的父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }


// ES6 的类的继承
// 创建一个 继承自 Person 的 Student 类
// extends 关键字相当于原型继承
class Student extends Person {
constructor (gender, name, age) {
// 相当于在调用父类构造函数体, 把 name 和 age 传递过去
// 相当于 call 继承
super(name, age)
this.gender = gender
}

play () { console.log('你好 世界') }
}

const s = new Student('男', 'Jack', 20)
console.log(s)

[js] 继承 原型继承,call继承,组合继承,ES6继承_子类_19



​​前端 JavaScript 之 继承 _ 原理版​​

​JavaScript学习笔记(二十九)-- 闭包与继承​