js继承基本上是面试官最喜欢考的一块内容了 😀
上篇已经讲述到js是基于原型的语言,所以它实现继承与传统OOP语言有很大差别,实现起来有些复杂。
当然越是复杂,咱们应当要掌握,这样面试官才能考察出学东西是否扎实,要克服,请仔细阅读。(骗阅读量🤑)
本章继上篇《对象》的最后一节给予补充——继承。
嗯嗯(清嗓)请各位搬好小板凳😎
里氏替换原则(Liskov Substitution Principle,LSP)
不仅传统OOP语言遵循着 里氏替换原则,js继承也同样遵循里氏替换原则。
-
如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2 时,程序P的行为没有发生变化,那么类型 S 是类型 T 的子类型。
-
所有引用基类的地方必须能透明地使用其子类的对象。
通过定义,我们可推出:
子类可以代表父类,再强烈可以说,子类就是父类,但父类不能代表子类。
子类的属性和行为继承父类的属性和行为。
子类可以有自己独特的属性和行为。
按照里氏替换这一原则,我们来实现分析并实现js的继承。
实现
将继承前,我们先创建一个完整的类。
- 这里我们先创建一个“类”
function Person(){}
- 给类创建自定义属性和行为
创建属性和行为分为两种:
①构造函数
function Person(){
this.name = 'ipenman'
this.work = '程序员'
this.show = function(){
console.log('My name is %s,My work is %s',this.name,this.work)
}
}
const p = new Person()
p.show() // My name is ipenman,My work is 程序员
②prototype
function Person(){}
Person.prototype.name = 'ipenman'
Person.prototype.work = '程序员'
Person.prototype.show = function (){
console.log('My name is %s,My work is %s',this.name,this.work)
}
const p = new Person()
p.show() // My name is ipenman,My work is 程序员
区别:
构造函数中的属性为私有属性,跟随实例。
prototype为共享属性,跟随函数,多个实例指向同一个prototype。
选用:
多个实例需要的共同属性或行为时,选用prototype,当私有属性时选用构造函数属性。
function Person(name,work){
this.name = name
this.work = work
}
Person.prototype.show = function(){
console.log('My name is %s,My work is %s',this.name,this.work)
}
const p1 = new Person('ipenman','程序员')
p1.show() // My name is ipenman,My work is 程序员
const p2 = new Person('zhangsan','学生')
p2.show() // My name is zhangsan,My work is 学生
ok,到此一个完整的类已经创建,终于到了我们的正题——继承。
根据里氏替换原则实现类式继承
子类就是父类(原型继承)
简单继承
function Person(name,work){
this.name = 'ipenman'
this.work = '程序员'
}
Person.prototype.show = function(){
console.log('My name is %s,My work is %s',this.name,this.work)
}
function Student(){
Person.call(this)
}
const s = new Student()
结果显示:
s => Student {name:"ipenman",work:"程序员"}
可以看到已经继承了name和work的属性。
但仔细观察,发现Student里面并没有Person的prototype的show函数
打印 s.show()会报错TypeError: s.show is not a function
显然,这种继承并不是我们想要的,因为它没有继承到原型Person.prototype,不完全符合里氏替换原则。
简单继承 + 原型继承
好嘛,上面的继承不是没有Person.prototype,那我就继承一下它呗
function Person(name,work){
this.name = 'ipenman'
this.work = '程序员'
}
Person.prototype.show = function(){
console.log('My name is %s,My work is %s',this.name,this.work)
}
function Student(){
Person.call(this)
}
Student.prototype = Object.create(Person.prototype) // 创建原型是Person.prototype的新对象赋给Student.prototype
const s = new Student()
嘿嘿,这不是把原型属性和构造函数的属性继承过去了吗?
到这,本“大家”就要夸赞一句了,很棒!
但是,不要飘啊,再仔细观察一下。
一顿观察后...
我去,Student的构造函数(constructor)也给继承过了。
本“大家”上面介绍了,函数的类就是它自身的构造函数,就是说Student.prototype.constructor 指向的是function Student,我们可以拿上面的最基础的Person函数打印下
所以说,这也不太对!
可能又有同学说了,我用同样的方式,把Student.prototype.constructor 显示指定到function Student不行吗?
好,满足你!
完全继承
function Person(name,work){
this.name = 'ipenman'
this.work = '程序员'
}
Person.prototype.show = function(){
console.log('My name is %s,My work is %s',this.name,this.work)
}
function Student(){
Person.call(this)
}
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student
const s = new Student()
这时候,我们差最后一道工序,看是否真正的达到了继承
console.log(s instanceof Student,s instanceof Person) // true true
es6 class 及继承
上面我们费了好大好大的功夫才实现继承,这对初学js的小伙伴太不友好了,Duang!es6 class驾到
创建一个类
class Person{
constructor(name,work){
this.name = name
this.work = work
}
show(){
console.log('My name is %s,My work is %s',this.name,this.work)
}
}
const p = new Person('ipenman','程序员')
这看起来很像传统OOP语言,实则class只是一个语法糖,只是看起来更像面向对象编程而已。当constructor没有参数时,可以省略constructor,class内部会补全!
继承
class Person{
constructor(name,work){
this.name = name
this.work = work
}
show(){
console.log('My name is %s,My work is %s',this.name,this.work)
}
}
class Student extends Person {
constructor(name,work){
super(name,work)
}
}
const s = new Student('zhangsan','学生')
s.show() // My name is zhangsan,My work is 学生
es6 应用 extend及super关键字 实现继承。
Es5继承 vs Es6继承
完整代码
function Person(name, work) {
this.name = 'ipenman'
this.work = '程序员'
}
Person.prototype.show = function() {
console.log('My name is %s,My work is %s', this.name, this.work)
}
function Student() {
Person.call(this)
}
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student
class Person1{
constructor(name,work){
this.name = name
this.work = work
}
show(){
console.log('My name is %s,My work is %s',this.name,this.work)
}
}
class Student1 extends Person1 {
constructor(name,work){
super(name,work)
}
}
const s1 = new Student1('zhangsan','学生')
s.show()
通过上图对比得出,我们实现的es5完美继承和es6 class 继承达到同样的效果!
最后
到这终于完成了,噫吁嚱,到这是否给各位小伙伴解惑了呢?点赞加关注哦!