目录
继承 :?
为什么需要继承???
构造函数 :?
常见的继承方案?:??
1.?原型继承?:?
?图解 :?
2.?call 继承?:
?扩展 : 第二种写法?
3.?组合继承?:?
4.?ES6?的继承语法?:
简图 :?
继承 :?
继承 :?
????????+?继承是一个关于??构造函数? 的高阶应用
????????+?继承一定是出现在?两个构造函数?之间的关系
????????+?什么是继承?:?
??????????=>?当?构造函数A?的实例,?使用了?构造函数B?原型上书写的属性和方法
??????????=>?此时,?我们就说?A?和?B?出现了继承关系
??????????=>?构造函数B?继承自?构造函数A
??????????=>?构造函数A?是?构造函数B?的父类
??????????=>?构造函数B?是?构造函数A?的子类
为什么需要继承???
????+?我们面向对象开发的核心,?是?=>? 高内聚 低耦合? <=
????+?来思考???进行假设?:?
??????=>?我书写一个?苹果类?:
????????~>?颜色,?品牌,?重量,?水分,?名称
????????~>?吃的方法:?削皮吃,?去核吃
??????=>?我书写一个?橘子类?:
????????~>?颜色,?品牌,?重量,?水分,?名称
????????~>?吃的方法:?剥皮吃,?吐籽吃
可以看到 : 苹果类 和 橘子类 有 共同的 部分
????+ 所以换一种书写方法?:?
??????=>?水果类?:?
????????~>?颜色,?品牌,?重量,?水分,?名称
??????=>?苹果类?:
????????~>?继承?水果类
????????~>?书写我独立的内容
????????~>?形状
??????=>?橘子类?:?
????????~>?继承?水果类
????????~>?书写我独立的内容
????????~>?额外用途
构造函数 :?
+?构造函数 :?
??=>?原型 ( prototype ) :
**每一个函数?天生自带一个属性,?叫做?prototype?,?是一个?对象数据类型?**
??=>?为什么需要原型 :
????~>?为了解决 构造函数 的不合理
????~>?书写 构造函数 的时候
????~>?属性 直接书写在 构造函数体内
????~>?方法 书写在 构造函数 的 原型 上
??=>?目的:?为了书写一些方法,?给到该 构造函数 的 实例 使用
????~>?把每一个实例都会用到的方法,?提取出来放在了 构造函数 的 原型 上
常见的继承方案?:??
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?赋值给?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) {
// 相当于在调用父类构造函数体, 把 name 和 age 传递过去
// 相当于 call 继承
super(name, age)
this.gender = gender
}
play () { console.log('你好 世界') }
}
const s = new Student('男', 'Jack', 20)
console.log(s)
?
?
简图 :?
|