目录
继承 :
继承 :
+ 继承是一个关于 构造函数 的高阶应用
+ 继承一定是出现在 两个构造函数 之间的关系
+ 什么是 继承 :
=> 当 构造函数 A 的 实例 , 使用了 构造函数 B 原型 上书写的属性和方法
=> 此时, 我们就说 A 和 B 出现了 继承关系
=> 构造函数 A 继承自 构造函数 B
=> 构造函数 A 是 构造函数 B 的 子类
=> 构造函数 B 是 构造函数 A 的 父类
为什么需要继承 ?
+ 我们面向对象开发的核心, 是 => 高内聚 低耦合 <=
+ 来思考 ? 进行假设 :
=> 我书写一个 苹果类 :
~> 颜色, 品牌, 重量, 水分, 名称
~> 吃的方法: 削皮吃, 去核吃
=> 我书写一个 橘子类 :
~> 颜色, 品牌, 重量, 水分, 名称
~> 吃的方法: 剥皮吃, 吐籽吃
可以看到 : 苹果类 和 橘子类 有 共同的 部分
+ 所以换一种书写方法 :
=> 水果类 :
~> 颜色, 品牌, 重量, 水分, 名称
=> 苹果类 :
~> 继承 水果类
~> 书写我独立的内容
~> 形状
=> 橘子类 :
~> 继承 水果类
~> 书写我独立的内容
~> 额外用途
构造函数 :
+ 构造函数 :
=> 原型 ( prototype ) :
**每一个函数 天生自带一个属性, 叫做 prototype , 是一个 对象数据类型 **
=> 为什么需要原型 :
~> 为了解决 构造函数 的不合理
~> 书写 构造函数 的时候
~> 属性 直接书写在 构造函数体内
~> 方法 书写在 构造函数 的 原型 上
=> 目的: 为了书写一些方法, 给到该 构造函数 的 实例 使用
~> 把每一个实例都会用到的方法, 提取出来放在了 构造函数 的 原型 上
A 继承自 C , A 是 子类 , C 是 父类
B 继承自 C , B 是 子类 , C 是 父类
继承的目的 , 就是为了能够使用 构造函数 C 身上的 属性 和 方法 ,
把公共的东西提取出来成为更公共的东西
常见的继承方案 :
1. 原型继承 :
原型继承(原型链继承) :
+ 核心: 让子类的 原型 指向 父类的 实例
+ 语法: 子类.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 方法
*/
// 子类
function Student(gender) {
this.gender = gender
}
// 当 Student 构造函数书写完毕以后
// 此时会伴生一个 Student.prototype 是一个 对象数据类型
// 我就可以向 Student.prototype 上添加一个成员
// 因为 Student.prototype 是一个对象
// Student.prototype.a = 100
// 现在不直接向这个对象上添加内容了
// 而是可以把 Student.prototype 重新赋值
// 本身你的 Student.prototype 保存的是一个 对象数据类型 的 地址
// 我给你赋值为一个新的 对象数据类型 的地址
// 就把你本身保存的地址覆盖了
const obj = {
message: '我是自定义的 obj 对象'
}
Student.prototype = obj
// Student.prototype 既然可以被赋值为一个对象, 那么就是任何一个对象都可以
// 那么我的 Person 的实例 p 也是一个 对象数据类型, p 是父类的实例对象
// 我就可以直接把 实例对象 p 赋值给 Student.prototype
// 因为这句代码的执行
// Student 的 实例 可以使用 Person 书写的属性和方法了
// 我们就说 Student 继承自 Person
// Student 是 Person 的 子类
// Person 是 Student 的 父类
// const p = new Person('Jack', 18)
// Student.prototype = p
// 进行一下 等量代换
Student.prototype = new Person('Jack', 18)
// s 是 Student 的实例
// s 所属的构造函数是 Student
// s.__proto__ 指向 所属构造函数的 Prototype
// 因为 Student.prototype === p
// s.__proto__ === p
const s = new Student('男')
console.log(s)
// 根据原型链的规则
// 对象访问
// 当你需要访问 s 的 gender 成员的时候
// s 自己本身就有, 直接使用
console.log(s.gender) // 自己有的属性 => 男
// 当你需要访问 s 的 name 成员的时候
// s 自己本身没有, 需要去到 自己的 __proto__ 上查找
// s 自己本身的 __proto__ 是 p, 所以就是在使用 p 身上的 name 成员
// p 是 Person 的实例, Student 的实例使用的是 Person 构造函数体内书写的 属性
console.log(s.name) // 打印 => Jack
// 当你需要访问 s 的 sayHi 成员的时候
// s 自己本身没有, 需要去到 自己的 __proto__ 上查找
// 因为自己的 __proto__ 上也没有, 去到再 __proto__ 上查找
// 又因为自己的 __proto__ 就是 p, 再__proto__ 就是 p.__proto__
// 其实就是去 Person.prototype 上查找的内容
// 此时 Student 的实例使用了 Person.prototype 上书写的 方法
s.sayHi() // 打印 => hello world
图解 :
2. call 继承 :
call 继承 ( 借用构造函数继承 ) :
+ 核心: 把父类构造函数体, 当做普通函数调用,
利用 call 方法调用父类构造函数 并 改变 this 指向
+ 语法: 在子类构造函数体内书写 父类.call(this)
优点:
+ 一个实例使用的属性可以在一个位置传递参数
+ 可以把继承来的属性直接出现在 子类的实例 身上
缺点:
+ 只能继承 父类构造函数体内 书写的内容, 父类构造函数 原型上 的内容不能继承
call 语法 回顾 :
call( ) :+ 语法:=> 函数名.call()=> 对象名.函数名.call()+ 参数:=> 第一个参数: 要修改的函数内的 this 指向=> 第二个参数开始: 依次给函数内每一个 参数 赋值+ 特点:=> 会立即调用函数+ 用处:=> 在立即执行的函数时才使用=> 事件处理函数, 和 定时器处理函数, 一般不会使用这个方法
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }
构造函数概念 :
+ 构造函数也是一个函数
+ 是函数就可以直接调用, 可以不和 new 连用
+ 只是不和 new 连用的时候, 没有自动创建对象的能力了
// 把 Person 当做一个普通函数调用
// 因为当做 普通函数 调用, Person 内的 this 指向谁 ? => window
// Person 函数体内的两个内容添加给 window 了
Person('Jack', 18)
console.log(window.name) // 打印 => Jack
console.log(window.age) // 打印 => 18
// 既然 Person 可以当做普通函数调用
// call 方法就是用来调用函数, 并且改变 this 指向的
const obj = { message: '我是自定义的一个 obj 对象' }
// 在调用 Person 的时候
// 使用 call 方法, 把 Person 内的 this 改成 obj
// Person 内书写的两个代码添加的成员, 加在 obj 身上了
Person.call(obj, 'Jack', 20)
console.log(obj)
// 子类
function Student(gender, name, age) {
this.gender = gender
}
Student.prototype.play = function () { console.log('你好 世界') }
const s = new Student('男')
// 因为 call 方法调用 Person 的时候, 把 Person 内的 this 修改为 s 了
// 所以 name 和 age 成员添加到了 s 身上
Person.call(s, 'Rose', 20)
console.log(s)
// 子类
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)
扩展 : 第二种写法
3. 组合继承 :
组合继承 :
+ 核心: 把 原型继承 和 call 继承 合并在一起就做组合继承
+ 优点:
=> 父类构造函数体内的属性和原型上的方法都可以继承
=> 继承下来的属性在子类实例对象自己的身上
+ 缺点:
=> 原型上会多一套继承下来的属性名
原型继承 优缺点 :
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)
}
// 实现原型继承
// 目的: 为了把 Person 的 prototype 内的内容继承下来
// 利用原型继承, 继承父类构造函数身上的方法
Student.prototype = new Person()
Student.prototype.play = function () { console.log('你好 世界') }
// 创建子类的实例
const s = new Student('男', '张三', 20)
console.log(s)
4. ES6 的继承语法 :
ES6 的继承方案 :
+ ES6 官方提出了 关键字 来实现继承
ES6 的继承语法:
+ 语法分成两个部分 :
1. 书写子类的时候, 使用 extends 关键字
=> class 子类名 extends 父类 { ... }
2. 在子类的 constructor 内书写
=> super( )
+ 注意:
=> 必须要两个条件同时书写
=> 在 constructor 内书写 super 的时候, 必须写在所有 this 的 最前面
( 必须要先继承 : 你必须要继承传统 , 然后再创新 )
=> 父类如果是一个 ES5 的构造函数, 那么可以正常继承
=> 可以继承自 ES6 的 类, 也可以继承自 ES5 的构造函数
// ES5 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }
//ES6 父类
class Person {
constructor (name, age) {
this.name = name
this.age = age
}
sayHi () { console.log('hello world') }
}
// ES6 的类的继承
// 创建一个 继承自 Person 的 Student 类
// extends 关键字相当于原型继承
class Student extends Person {
constructor (gender, name, age) {
// 你必须要先继承传统 , 然后再创新
super(name, age)
// 相当于在调用父类构造函数体, 把 name 和 age 传递过去
// 相当于把父类的属性继承在自己的身上
// 相当于 call 继承
this.gender = gender
}
play () { console.log('你好 世界') }
}
const s = new Student('男', 'Jack', 20)
console.log(s)