2022.11.04
一.原型继承
继承
+ 出现在两个构造函数之间的关系
+ 当 构造函数A 的实例 可以使用 构造函数B 的属性和方法
+ 就说 构造函数A 继承自 构造函数B
+ 构造函数A 是 构造函数B 的子类
+ 构造函数B 是 构造函数A 的父类
+ 市场上常见的继承方案
=> 原型 / 借用 / 组合 / 寄生 / 冒充 / 拷贝 / ...
原型继承
+ 原理: 让子类的原型指向父类的实例
+ 代码: 子类.prototype = new 父类
+ 依据: 依赖原型链的对象访问机制实现的
+ 优点:
=> 可以继承父类构造函数体内的属性, 也可以继承父类构造函数原型上的方法
+ 缺点:
=> 继承来的属性不在自己身上
=> 子类的所有实例, 继承来的属性值是一样的
/*
原型链: 提供对象访问机制
+ 当你访问一个对象成员的时候
+ 首先在自己身上查找, 如果有直接使用
+ 如果没有, 去到自己的 __proto__ 查找, 如果有直接使用
+ 如果还没有, 就再去 __proto__ 查找, 如果有直接使用
+ 以此类推, 直到 Object.prototype 都没有, 返回 undefined
例子: 假设有一个对象是 o
+ o.name
+ 如果 o 对象自己本身就有, 直接使用
+ 如果 o 对象没有, 就查找 o.__proto__ 是否存在, 有就直接使用
+ 如果 o.__proto__ 也没有, 那么就自动再去 o.__proto__.__proto__ 查找, 如果有就直接使用
+ 以此类推, 如果走到了 Object.prototype 上都没有, 那么返回 undefined
*/
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('Person 原型上的 sayHi 方法') }
function Student(gender) {
this.gender = gender
}
// 得到一个 Person 的实例
const p = new Person('张三', 18)
// 修改原型对象
Student.prototype = p
const s = new Student('男')
// 当你方法 s 的 gender 属性的时候
// 自己有直接使用
console.log(s.gender)
// 当你访问 s 的 name 属性的时候
// 自己没有, 去到自己的 __proto__ 查找(s.__proto__)
// 因为 s.__proto__ === Student.prototype
// 又因为 Student.prototype === p
// 所以 s.__proto__ === p
// p 是 Person 的实例对象, 身上有 name 和 age 属性
// s.__proto__ 身上就有 name 和 age 属性
console.log(s.name)
// 当你访问 s 的 sayHi 方法的时候
// 自己没有, 去到自己的 __proto__ 查找(s.__proto__ === p), 发现也没有
// 再次去 __proto__ 查找(s.__proto__.__proto__)
// 因为 s.__proto__ === p
// 所以 s.__proto__.__proto__ === p.__proto__
// 因为 p 是 Person 构造函数的实例
// 所以 p.__proto__ === Person.prototype
// 所以 s.__proto__.__proto__ === Person.prototype
s.sayHi()
const s2 = new Student('女')
console.log(s2.gender)
console.log(s2.name)
二.借用继承
借用继承(借用构造函数继承 / call继承)
+ 原理: 把父类构造函数当做普通函数使用
+ 代码: 父类.call(this)
+ 实现: 利用 call 方法改变函数内的 this 指向
-
优点:
=> 继承来的属性直接出现在自己身上, 并且每一个子类的实例继承来的属性都可以保证值不一样 -
缺点:
=> 只能继承构造函数体内的属性
=> 不能继承构造函数原型上的方法// 父类 function Person(name, age) { this.name = name this.age = age } Person.prototype.sayHi = function () { console.log('Person 原型上的 sayHi 方法') } // const pp = new Person() // console.log(pp); // 子类 function Student(gender,...ab) { this.gender = gender // 这里的 this 是谁 ? Student 的实例对象(s) // 在这里把 父类构造函数 当做普通函数调用一下, 并且利用 call 来改变 this 指向 // Person 函数内的 this 指向了 Student 的实例对象 // Person 函数执行的时候, 函数体内的代码就在想 Student 的实例上添加了若干成员 Person.call(this,...ab) } const s = new Student('男', '李四', 22) console.log(s) const s2 = new Student('女', '王五', 18) console.log(s2) // Person 是一个构造函数 // 调用的时候和 new 关键字连用, 函数内的 this 指向 本次被自动创建的对象(当前实例对象) // 函数体内的代码执行的时候, 就是想 实例对象 上添加成员 // 但是 Person 的本质是一个函数, 就可以不和 new 连用 // 不会报错, 也能执行代码, 只是没有了创建实例的能力 // 因为直接调用的时候 this 指向 window, 所以 name 和 age 成员添加到 window 身上了 // 只要 this 指向哪里, 这些成员就添加到哪里 // Person('张三', 18) // call 方法只要是函数就能使用 // 作用: 改变函数内的 this 指向 // 语法: 函数名.call() // 第一个参数: 该函数内的 this 指向 // 第二个参数开始, 依次给函数进行形参赋值 // const obj = { a: 100, b: 200 } // 利用 call 调用 Person 函数 // Person 函数内的 this 指向 obj, 那么函数内的代码执行的时候 // 向 obj 上添加成员 // Person.call(obj, '李四', 20) // console.log(obj)
三.组合继承
/*
组合继承
+ 把 原型继承 和 借用继承 组合在一起使用
*/
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('Person 原型上的 sayHi 方法') }
// 子类
function Student(gender, name, age) {
this.gender = gender
// 借用继承(目的: 把属性继承在子类身上)
Person.call(this, name, age)
}
// 原型继承(目的: 继承父类原型上的方法)
Student.prototype = new Person()
Student.prototype.play = function () { console.log('Student 原型上的 play 方法') }
const s = new Student('男', '张三', 18)
console.log(s)
四.拷贝继承
拷贝继承
+ 一种继承方案
+ 利用 for in 循环来实现继承
+ for in 循环作用: 遍历对象
/*
in 关键字
+ 语法: 字符串 in 对象
+ 结果: 一个布尔值
=> true: 表示该对象能访问到这个 键名
=> false: 表示该对象访问不到这个 键名
+ 判断的就是该对象能不能访问到这个 key
=> 不管是在自己身上, 还是在原型上, 都为 true
*/
/*
hasOwnProperty() 方法
+ 对象可以使用的方法
+ 语法: 对象.hasOwnProperty('键名')
+ 返回值: 一个布尔值
=> true: 说明该对象自身有这个 key
=> false: 说明该对象自身没有这个 key
+ 注意: 原型上的不算
*/
拷贝继承 - 方式1
+ 创建一个父类的实例
+ 遍历父类的实例, 把每一项依次添加到子类的原型上
父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('Person 原型上的 sayHi 方法') }
// 创建一个父类的实例
const p = new Person('张三', 18)
//此时 p 是一个对象
//自己身上: name age
//原型上: sayHi
//如果你使用 for in 循环遍历的时候, 能访问到 name age sayHi 三个成员
for (let k in p) {
console.log(k)
}
//子类
function Student(gender) {
this.gender = gender
}
// 实现继承
// 把 p 的所有内容拷贝一份到 子类的原型
for (let k in p) {
Student.prototype[k] = p[k]
}
Student.prototype.play = function () { console.log('Student 原型上的 play 方法') }
console.log(Student.prototype)
// 创建子类的实例
const s = new Student('男')
console.log(s)
console.log(s.name)
console.log(s.age)
s.sayHi()
s.play()
拷贝继承 - 方式2
+ 把 拷贝继承 的一部分 和 借用继承 组合在一起使用
+ 拷贝继承的一部分
=> 之前的拷贝继承, 拷贝的是 父类的实例
-> 会复制的内容是 实例的成员 和 原型的成员
=> 现在只拷贝 父类的原型
=> 一部分: 拷贝父类的原型复制到子类的原型
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('Person 原型上的 sayHi 方法') }
Person.prototype.sayHi2 = function () { console.log('Person 原型上的 sayHi2 方法') }
Person.prototype.sayHi3 = function () { console.log('Person 原型上的 sayHi3 方法') }
// 子类
function Student(gender, name, age) {
this.gender = gender
// 利用借用继承, 继承父类的属性
Person.call(this, name, age)
}
// 实现继承
// 利用拷贝继承继承父类的原型
for (let k in Person.prototype) {
Student.prototype[k] = Person.prototype[k]
}
Student.prototype.play = function () { console.log('Student 原型上的 play 方法') }
console.log(Student.prototype)
const s = new Student('男', '李四', 20)
console.log(s)
五.冒充继承
冒充继承
+ 利用 Object.assign() 进行数据浅拷贝
+ 原理:
=> 创建一个父类的实例
=> 直接把父类的实例的内容浅拷贝一份到子类的实例上
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('Person 原型上的 sayHi 方法') }
Person.prototype.sayHi2 = function () { console.log('Person 原型上的 sayHi2 方法') }
Person.prototype.sayHi3 = function () { console.log('Person 原型上的 sayHi3 方法') }
// const p = new Person(name, age)
// 子类
function Student(gender, name, age) {
this.gender = gender
const p = new Person(name, age)
// 实现浅拷贝
// 这里的 this 是 Student 的实例对象
// p 是父类的实例对象
// 把父类实例对象的所有内容拷贝到子类的实例身上
Object.assign(this, p)
}
// 问题: 子类其实没有自己的独立原型
Student.prototype = Person.prototype
Student.prototype.play = function () { console.log('Student 原型上的 play 方法') }
const s = new Student('男', '张三', 18)
console.log(s)
s.name = 'lala'
console.log(s);
六.寄生继承
寄生继承
+ 从 组合继承 一点一点演化过来
+ 需要利用到第三个没用的构造函数
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('Person 原型上的 sayHi 方法') }
Person.prototype.sayHi2 = function () { console.log('Person 原型上的 sayHi2 方法') }
Person.prototype.sayHi3 = function () { console.log('Person 原型上的 sayHi3 方法') }
// 准备第三个构造函数, 当做桥梁使用
function Third() {}
// 需求: 把 Person.prototype 上的所有内容放在 t 对象身上
// 方案1: 浅拷贝
// 方案2: for in 循环
// 把 Person 的 prototype 上的所有内容放在 Third 的 prototype 上
// 因为放在 Third.prototype 上, t 也能用
// 又不想使用 for in 循环
// 私人: 让 Third 的原型 寄生 Person 的 原型
Third.prototype = Person.prototype
// Third 就是作为一个桥梁出现
// 我不需要自己的原型, 我自己也不需要任何属性
const t = new Third()
console.log(t)
// 子类
function Student(gender, name, age) {
this.gender = gender
// 利用借用继承继承下父类的属性
Person.call(this, name, age)
}
// 实现继承
// 让子类的原型继承自 桥的 实例
Student.prototype = t
// Student 也相当于没有自己的原型了, Student 的原型其实就是 桥的实例对象
// 实际上就相当于添加在桥上了
Student.prototype.play = function () { console.log('Student 原型上的 play 方法') }
const s = new Student('男', '张三', 18)
console.log(s)
七.寄生继承升级
-
利用闭包的形式把所有的内容包裹起来
// 父类 function Person(name, age) { this.name = name this.age = age } Person.prototype.sayHi = function () { console.log('Person 原型上的 sayHi 方法') } Person.prototype.sayHi2 = function () { console.log('Person 原型上的 sayHi2 方法') } Person.prototype.sayHi3 = function () { console.log('Person 原型上的 sayHi3 方法') } // 书写一个子类 const Student = (function () { // 在这个自执行函数内, 实现继承逻辑 function Student(gender, name, age) { this.gender = gender Person.call(this, name, age) } // 桥梁 构造函数 function Third() {} Third.prototype = Person.prototype const t = new Third() // Student 继承 桥梁 Student.prototype = t // 子类自己的原型上的方法 Student.prototype.play = function () { console.log('play 方法') } return Student })() // 全局还有 Student 变量吗 ? 没有了 // 所以把全局的变量直接定义为 子类的 构造函数名即可 const s = new Student('男', '张三', 18) console.log(s)
八.类的继承
类的继承
+ 专门对应 class 语法实现的继承
+ 实现:
=> 步骤1: 在创建子类的时候, 直接使用 extends 关键字继承自父类
-> 语法: class 子类名 extends 父类名 {}
=> 步骤2: 在子类的 constructor 内使用 super 来继承父类的属性
-> 语法: super(参数)
-> 注意: 在子类的 constructor 内, super 必须写在最前面
在继承的过程中
+ 父类有一些内容不希望被继承
+ 我们管不希望被继承的属性和方法叫做 类的静态成员
+ 添加一个 static 关键字
=> 一旦加了 static 关键字
=> 就是给 类 本身使用的
=> 不在给 类 的实例使用
=> 所以就继承不了了
// 父类写法一
class Person {
constructor (name, age) {
this.name = name
this.age = age
}
// 写出来可能会被继承个子类使用
sayHi1 () { console.log('sayHi1') }
sayHi2 () { console.log('sayHi2') }
// 不想被继承给子类使用
// 不能被 Person 的实例使用了
// 是给 Person 这个类自己使用的
static sayHi3 () { console.log('sayHi3') }
}
// 父类写法二
// function Person(name, age) {
// this.name = name
// this.age = age
// }
// Person.prototype.sayHi1 = function () { console.log('Person 原型上的 sayHi 方法') }
// Person.prototype.sayHi2 = function () { console.log('Person 原型上的 sayHi2 方法') }
// Person.prototype.sayHi3 = function () { console.log('Person 原型上的 sayHi3 方法') }
// 子类
// 一个继承自 Person 的 Student 子类
// extends 关键字就等价于 继承父类原型上的方法
class Student extends Person {
constructor (gender, name, age) {
// 相当于在继承父类的属性
// 等价于原先的 借用继承(目的: 继承构造函数体内的属性)
super(name, age)
this.gender = gender
}
play () { console.log('play') }
}
const s = new Student('男', '张三', 18)
console.log(s)
s.sayHi3()