重学js系列——继承

827 阅读5分钟

js继承基本上是面试官最喜欢考的一块内容了 😀

上篇已经讲述到js是基于原型的语言,所以它实现继承与传统OOP语言有很大差别,实现起来有些复杂。
当然越是复杂,咱们应当要掌握,这样面试官才能考察出学东西是否扎实,要克服,请仔细阅读。(骗阅读量🤑)
本章继上篇《对象》的最后一节给予补充——继承。

嗯嗯(清嗓)请各位搬好小板凳😎

里氏替换原则(Liskov Substitution Principle,LSP)


不仅传统OOP语言遵循着 里氏替换原则,js继承也同样遵循里氏替换原则。

  • 如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2 时,程序P的行为没有发生变化,那么类型 S 是类型 T 的子类型。

  • 所有引用基类的地方必须能透明地使用其子类的对象。

通过定义,我们可推出:
子类可以代表父类,再强烈可以说,子类就是父类,但父类不能代表子类。
子类的属性和行为继承父类的属性和行为。
子类可以有自己独特的属性和行为。

按照里氏替换这一原则,我们来实现分析并实现js的继承。

实现

将继承前,我们先创建一个完整的类。

  1. 这里我们先创建一个“类”
function Person(){}
  1. 给类创建自定义属性和行为


创建属性和行为分为两种:

①构造函数

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()

image.png

结果显示:
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()

image.png

嘿嘿,这不是把原型属性和构造函数的属性继承过去了吗?
到这,本“大家”就要夸赞一句了,很棒!
但是,不要飘啊,再仔细观察一下。
一顿观察后...
我去,Student的构造函数(constructor)也给继承过了。
本“大家”上面介绍了,函数的类就是它自身的构造函数,就是说Student.prototype.constructor 指向的是function Student,我们可以拿上面的最基础的Person函数打印下

image.png

所以说,这也不太对!
可能又有同学说了,我用同样的方式,把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

71432BC1C1BBF831414AEDC71F3946F5.gif

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()

image.png

通过上图对比得出,我们实现的es5完美继承和es6 class 继承达到同样的效果!

5DC08944.jpg

最后

到这终于完成了,噫吁嚱,到这是否给各位小伙伴解惑了呢?点赞加关注哦!

参考

《ES6标准入门》
《JavaScript权威指南》