??对象编程很重要的一方面,就是对象的继承。A对象通过继承B对象,就能直接拥有B对象的属性和方法。这对于代码的复用是非常有用的。
??大部分面向对象的编程语言,都是通过“类”(class)实现对象的继承。传统上,JavaScript语言的继承不通过class(ES6引入了class语法),而是通过“原型对象”(prototype)实现。
在正题开始之前,先问自己两个问题:
1. 继承解决了什么问题?为什么要使用继承?
2. 如何实现继承?
通过以下图片我们可以看出不同的类,存在着大量的重复代码,这样导致难以维护。所以需要进行封装,需要继承
function Person(name,age){
this.name=name
this.age=age
}
Person.prototype.say=function(){
console.log(this.name,"hello")
}
//继承
function Student(name,age,grade){
// Person.call(this,name,age)
Person.apply(this,[name,age])
this.grade=grade
}
//原型继承
Student.prototype = new Person()
//在继承的基础上增加方法
Student.prototype.printGrade = function(){
console.log(this.grade,100)
}
上面代码,存在两个类(Person,Student),那么二者在内存中的表现如何:
类与类之间相互独立,毫无相关。那么应该怎么产生关联呢?
上图表示,需要创建一个中间对象,那么改中间对象的作用:
- 子类的函数原型对象指向中间对象
- 中间对象的原型指向父类的函数原型对象
最后的结果就是子类和父类间接挂钩,继承也就在此诞生。好啦,问了自己两个问题并且搞清楚之后,正题就可以开始啦。
子类直接调用父类函数
function Person(name,age){
this.name=name
this.age=age
}
Person.prototype.say=function(){
console.log(this.name,"hello")
}
//继承
function Student(name,age,grade){
//call、apply都可以借用父类的代码
// Person.call(this,name,age)
Person.apply(this,[name,age])
this.grade=grade
}
解决了传递参数的问题
仅仅继承了父类的属性,并没有继承原型上的方法
将子类的函数原型(prototype)指向父类的函数原型(prototype),这就是原型链继承
Student.prototype = Person.prototype
是以上这种形式的吗?当然不是,这样写的话,针对一个子类也还行,但是存在多个子类。都是使用了同一个原型(即父类的原型),那么子类与子类之间就会相互影响。那么该如何写呢?
//原型继承
Student.prototype = new Person()
//在继承的基础上增加方法
Student.prototype.printGrade = function(){
console.log(this.grade,100)
}
//拆分来写
var obj = new Person()
Student.prototype = obj
创建一个实例对象obj,obj就是所谓的中间对象,那么子类的原型指向它,子类与子类之间就不会相互影响。
function Person(name,age){
this.name = name
this.age = age
}
Person.prototype.say=function(){
console.log(this.name,"hello")
}
//继承
function Student(name,age,grade){
// Person.call(this,name,age)
Person.apply(this,[name,age])
this.grade = grade
}
//原型继承
Student.prototype = new Person()
//在继承的基础上增加方法
Student.prototype.printGrade = function(){
console.log(this.grade,100)
}
var obj = new Student("xiaoming",18,100)
console.log(obj)
obj.say()
obj.printGrade()
- 参数不好传递(子类的name不好传递到父类中)
- 子类的实例对象类型错误,继承的name属性也没有显示
类型错误的原因:Student.prototype.constructor.name,由于本身丢失,中间对象又没有constructor属性,就会寻找到Person的函数原型上,所以类型为Person
&emps;&emps;为了解决原型链继承中存在的问题,开发人员提供了一种新的技术:constructor stealing(有很多名称:借用构造函数或者称之为经典继承或者称之为伪造对象)
借用继承的做法非常简单:在子类型构造函数的内部调用父类型构造函数
function Person(name,age){
this.name=name
this.age=age
}
Person.prototype.say=function(){
console.log(this.name,"hello")
}
//继承
function Student(name,age,grade){
// Person.call(this,name,age)
Person.apply(this,[name,age])
this.grade=grade
}
//原型继承
Student.prototype = new Person()
//在继承的基础上增加方法
Student.prototype.printGrade = function(){
console.log(this.grade,100)
}
var obj = new Student("xiaoming",99,11)
console.log(obj)
obj.say()
obj.printGrade()
- 解决了传递参数的问题
- 解决了子类的类型不对的问题(指定子类的constructor)
- 构造函数会被调用两次:一次在创建子类型原型对象的时候,一次在创建子类型实例的时候
- 父类型的属性(示例:name属性)会有两份:一份在中间对象中,一份在子类型示例中
&emps;&emps;这种模式要从道格拉斯·克罗克福德(Douglas Crockford,著名的前端大师,JSON的创立者)。在2006年写的一篇文章说起:Prototypal Inheritance in JavaScript(在JS中使用原型式继承)
怎么实现继承?需要中间对象,最开始我们就已经提及了。
那么是不是可以自己定义一个对象,使其_proto_指向另外一个对象呢?
可以定义一个函数,该函数接受一个对象作为参数,函数体内部创建一个新的对象,使其新对象的原型指向参数对象,然后返回新的对象。实现类似功能的函数,被称为原型式继承函数
function createObject1(obj) {
function Fn() {}
Fn.prototype = obj
return new Fn()
}
function createObject2(obj) {
var newObj = {};
Object.setPrototypeOf(newObj, obj); // newObj的原型指向obj
return newObj;
}
const newObj = Object.create(obj)
既然有了原型式继承函数,那么就可以实现对象与对象之间的继承,类与类之间的继承了
const Person = {
name: "父类",
play: function () {
console.log("play~~");
},
};
const obj = Object.create(Person);
console.log(obj); // {}
console.log(obj.__proto__); // { name: '父类', play: [Function: play] }
Student.prototype = new Person()
?
// 修改成:
Student.prototype = Object.create(Person.prototype)
function Person(name,age){
this.name=name
this.age=age
}
Person.prototype.say=function(){
console.log(this.name,"hello")
}
//继承
function Student(name,age,grade){
// Person.call(this,name,age)
Person.apply(this,[name,age])
this.grade=grade
}
//原型继承
Student.prototype = Object.create(Person.prototype)
//在继承的基础上增加方法
Student.prototype.printGrade = function(){
console.log(this.grade,100)
}
var obj = new Student("小明",18,100)
console.log(obj)
obj.say()
obj.printGrade()
创建中间对象简单,容易实现继承
必须与其他方式的继承才能实现完成的继承
ES6中引入了class关键字,class可以通过extends关键字实现继承,还可以通过static关键字定义类的静态方法,这比ES5的通过修改原型链实现继承要清晰和方便很多。
ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Person.apply(this)。ES6的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法,然后再用子类的构造函数修改this))
需要注意:class关键字只是原型的语法糖,JavaScript继承仍然是基于原型实现的
//父类
class Person{
//构造函数
constructor(name,age){
this.name = name
this.age = age
}
say(){
console.log(this.name,"hello")
}
}
//子类
//extends 原型继承
class Student extends Person{
constructor(name,age,grade){
//super 超类
super(name,age) //Person.call(this,name,age)
this.grade = grade
}
say(){
//如果想要先打印父类中的say方法,就需要使用super去调用父类中的say方法即可
super.say()
console.log(this.name,"您好")
}
}
var obj = new Student("小明",100,100)
console.log(obj)
obj.say()
代码量少,思路清晰,便于理解
浏览器兼容性的问题。ie10及以下都不支持。
ES6中使用到了super关键字,欢迎大家对[ES6]Class继承-super关键字这篇博客提出需要改善的建议
在JavaScript中,面向对象继承有多种实现方式,每种方式都有其优缺点。选择合适的继承方式取决于具体的需求和场景。通过本文的介绍,希望各位读者能够更好的理解JavaScript中的面向对象继承,并能够灵活运用于实际开发中。有任何问题,可以通过评论进行交流~!