认识继承
// 构造函数1
function Person(name) {
this.name = name
}
Person.prototype.init = function() {
console.log('我是Person原型上的方法')
}
// 构造函数2
function Stu(age) {
this.age = age
}
Stu.prototype.sayHi = function() {
console.log('你好')
}
const s1 = new Stu(18)
console.log(s1)
console.log(s1.__proto__)
/**
* 如果s1对象内部 具有一个属性为name
* 并且s1还可以使用init方法
* 那么 我们就可以说Stu这个构造函数 继承自Person构造函数
* Stu 是 Person 的子类
* Person 是 Stu 的父类
**/
原型继承
- 利用自定义原型的方式实现继承关系
- 核心: 将子类的原型修改为父类的实例化对象
- 优点: 可以使用父类的属性和方法,实现了继承
- 缺点:
-
- 原本原型上的方法不能用了(因为 原型对象被改变了)
-
- 继承到的属性并不在自己身上,而是在原型对象上(不过不影响使用)
-
// 构造函数1
function Person(name) {
this.name = name
}
Person.prototype.init = function() {
console.log('我是Person原型上的方法')
}
// 构造函数2
function Stu(age) {
this.age = age
}
// Stu.prototype = {
// a: 1,
// b: 2
// }
Stu.prototype = new Person('张三')
Stu.prototype.sayHi = function() {
console.log('你好')
}
console.log('Stu原型对象', Stu.prototype)
const s1 = new Stu(18)
console.log('Stu 实例化对象:', s1)
console.log(s1)
// console.log(s1.name)
/**
* 访问对象s1的name属性
* 1. 先在对象本身查找,找到就直用,但是自身就只有一个age属性,所以没找到
* 2. 所以会去自己的__proto__对象中查找,也就是自己构造函数的原型对象
* 2.1 但是现在 自己构造函数的原型对象 已经被我们修改为Person这个构造函数的实例化对象
* 2.2 这个实例化对象上是具有name属性的,并且还有一个 init方法
* 3. 所在实例化对象中找到了name属性,值为:张三
* */
// s1.init()
s1.sayHi()
借用构造函数继承
- 核心: 把父亲构造函数当做普通函数调用,利用call修改这个函数内部的this指向(如果不修改的话,函数的this指向了其他的对象)
- 优点: 把父类的属性全都继承在了自己身上
- 缺点:
-
- 只能继承父类的属性,不能继承父类的方法
-
- 每次调用Stu的时候,Stu内部还会自动调用一次Person函数
-
// 构造函数1
function Person(name) {
this.name = name
}
Person.prototype.init = function() {
console.log('我是Person原型上的方法')
}
// 构造函数2
function Stu(age, name) {
// 1. 自动创建出来一个对象(这个函数内部的 this 就指向了这个对象,所以你可以通过 this向着个对象上添加属性)
// 2. 手动向对象上添加属性
this.age = age
// 将Person内部的this 修改为了第一步被自动创建出来的对象,并传递一个参数name给Person 函数使用
Person.call(this, name)
// 3. 自动返回这个对象
}
Stu.prototype.sayHi = function() {
console.log('你好')
}
const s1 = new Stu(18, '张三')
console.log(s1)
s1.init() //报错,无法继承方法
/**
* 分析
* 1. const s1 = new Stu(18, '张三') 通过new 关键字 调用Stu这个构造函数,得到一个实例化对象,存储在了常量s1内部
*
* 调用Stu函数时发生的事情
* 1. 给对象上添加一个age属性,并将形参的值 赋值给它
* 2. 调用Person并通过 call方法 改变了这个Person这个函数内部的this指向
* 2.1 所以现在Person函数内部的this 就相当于是 Stu 构造函数内部被自动创建出来的对象
* 调用Person函数是发生的事情
* this.name = 形参
* 3. 代码执行完毕之后,new Stu 时内部被自动创建出来的对象被添加了两个属性
* 3.1 是在Stu函数内部添加的age属性
* 3.2 是在Person 函数内部添加的name属性
* */
组合继承
- 核心: 把原型继承与借用构造函数继承结合起来使用
- 优点: 实例化对象上具有继承到的属性,并且能够继承到父类原型上的方法
- 缺点: 实例化对象上与原型对象上,都有父类的属性(多了一套属性,但是并不影响使用)
// 构造函数1
function Person(name) {
this.name = name
}
Person.prototype.init = function() {
console.log('我是Person原型上的方法')
}
// 构造函数2
function Stu(age, name) {
this.age = age
// 1. 借用构造函数继承,得到父类的属性(放在了 对象上,并且没有继承父类原型上的方法)
Person.call(this, name)
}
// 2. 利用原型继承,得到父类的属性(原型上)与方法
Stu.prototype = new Person('这个字符串没有意义')
Stu.prototype.sayHi = function() {
console.log('你好')
}
// console.log(Person.prototype)
// const s2 = new Person()
// console.log(s2.init)
// 创建实例化对象
const s1 = new Stu(18, '张三')
console.log('Stu实例化对象', s1)
// console.log('Stu原始对象', Stu.prototype)
// console.log(s1.name)
console.log(Stu.prototype)
console.log(s1.__proto__)
// s1.init()
/**
* 访问s1对象的name属性
* 1. 去对象自身内部查找,现在找到了,直接使用,并且停止查找
* */
拷贝继承
- 补充: for...in遍历,可以遍历到对象的原型上的方法
// 构造函数1
function Person(name) {
this.name = name
}
Person.prototype.init = function() {
console.log('我是Person原型上的方法')
}
// const p1 = new Person('张三')
// for(let key in p1) {
// console.log(key, p1[key])
// }
// 构造函数2
function Stu(age, name) {
this.age = age
/**
* 在 子类构造函数实例化父类构造函数,得到父类构造函数前实例化对象
* 然后利用 for...in 可以遍历到原型上的属性这个特点,将实例化对象的属性与其原型上的方法
* 一起拷贝到子类构造函数的原型中
* */
const p1 = new Person('张三')
// 拷贝循环
for(let key in p1) {
// console.log(key, p1[key])
Stu.prototype[key] = p1[key]
}
}
Stu.prototype.sayHi = function() {
console.log('你好')
}
// 创建实例化对象
const s1 = new Stu(18, '张三')
console.log('Stu 实例化对象', s1)
console.log('Stu 原型对象', s1.__proto__)
// console.log(Stu.prototype)
ES6类的继承
- 语法要求:
-
- 书写了类的时候:class 子类类名 extends 父类类名 {...}
-
- 书写 constructor 的时候: 内部需要书写super('父类需要的参数')
-
- 注意:
-
- extends 和 super 必须同时出现才能完成继承
-
- super 必须出现在constructor 的第一行
-
- 额外扩展:ES6 类也能继承ES5的构造函数
- 验证方法: 将Person ES6类的写法更改为ES5的构造函数写法即可
// 父类
class Person {
constructor(name) {
this.name = name
}
init() {
console.log('我是 Person 原型上的方法')
}
}
// 子类
class Stu extends Person {
constructor(age) {
super('父类需要的参数,都写在这里边') //必须写在第一行
this.age = age
}
sayHi() {
console.log('你好~~~')
}
}
const s1 = new Stu(18)
console.log(s1)
console.log(s1.name)
s1.init()