在JavaScript
中,相比于传统的面向对象编程语言使用类(class)
来实现继承关系,JS的做法是利用原型对象(prototype)
来实现继承。虽然在 ES6 中引入了class
语法,但实质上该语法仍然基于原型继承实现(语法糖)。
今天让我们来学习下六种继承的方式
第一种 原型链的继承
js
function Person() {
this.name = 'Person'
}
Person.prototype.eating = function () {
console.log(this.name + 'eating~')
}
// 子类:特有属性和方法
function Student() {
this.sno = 111
}
const p = new Person()
Student.prototype = p
Student.prototype.studying = function () {
console.log(this.name + ' studying~')
}
const stu = new Student()
console.log(stu.name) // Person
stu.eating() // Person eating~
stu.studying() // Person studying~
弊端有很多
1.第一个弊端:打印stu对象,继承的属性是看不到的
2.直接修改对象上的属性,是给本对象添加了一个新属性 获取引用,修改引用中的值,会相互影响
3.第三个弊端:在前面实现类的过程中都没有传递参数
第二种 借用构造函数继承
js
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.eating = function () {
console.log(this.name + ' eating~')
}
function Student(name, age, friends, sno) {
Person.call(this, name, age, friends)
this.sno = 111
}
const p = new Person()
Student.prototype = p
Student.prototype.studying = function () {
console.log(this.name + ' studying~')
}
const stu = new Student('Student', 18, ['kobe'], 111)
console.log(stu.name) // Student
stu.eating() // Student studying~
stu.studying()//Student eating~
原型链继承的弊端基本解决
但是借用构造函数也会有自己的弊端:
1.第一个弊端: Person函数至少被调用了两次
2.第二个弊端: stu的原型对象上会多出一些属性, 但是这些属性是没有存在的必要
第三种 组合继承
js
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.eating = function () {
console.log(this.name + ' eating~')
}
//子类
function Student(name, age, friends, sno) {
Person.call(this, name, age, friends)
this.sno = 111
}
// 直接将父类的原型赋值给子类, 作为子类的原型
// 直接将 Person.prototype 赋值给 Student.prototype 会导致 Student.prototype 和 Person.prototype 指向同一个对象,因此对 Student.prototype 的修改也会影响到 Person.prototype,因此使用`Object.create` 来创建一个新的对象,并将其赋值给 `Student.prototype`,以保持继承关系的正确性
Student.prototype = Object.create(Person.prototype)
Student.prototype.studying = function () {
console.log(this.name + ' studying~')
}
const stu = new Student('student', 18, ['friend'], 111)
console.log(stu) // Person { name: 'student', age: 18, friends: [ 'friend' ], sno: 111 }
stu.eating() // student eating~
组合继承结合了原型链继承和构造函数继承
的优点。首先使用构造函数继承来继承父对象的属性,然后将父对象的原型赋值给子对象的原型
,实现对父对象原型上方法的继承。
第四种 原型式继承
js
let obj = {
name: 'why',
age: 18
}
let info = Object.create(obj)
console.log(info)
console.log(info.__proto__)
弊端
1.原型链继承多个实例的引用类型属性指向相同。修改引用中的值,会相互影响。
2.无法传递参数
第五种 寄生式继承
js
let personObj = {
running: function () {
console.log('running')
}
}
function createStudent(name) {
let stu = Object.create(personObj)
stu.name = name
stu.studying = function () {
console.log('studying~')
}
return stu
}
let stuObj = createStudent('why')
let stuObj1 = createStudent('kobe')
let stuObj2 = createStudent('james')
弊端与原型式继承一样
1.原型链继承多个实例的引用类型属性指向相同。修改引用中的值,会相互影响。
2.无法传递参数
第六种 寄生组合式继承
js
//将SubType的原型对象指向SuperType的原型对象
function inheritPrototype(SubType, SuperType) {
SubType.prototype = Object.create(SuperType.prototype)
// 重新定义 SubType 的 constructor 属性
Object.defineProperty(SubType.prototype,'constructor',{
enumerable: false,
configurable: true,
writable: true,
value: SubType
})
}
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.running = function () {
console.log('running~')
}
function Student(name, age, friends, sno, score) {
Person.call(this, name, age, friends)
this.sno = sno
this.score = score
}
inheritPrototype(Student, Person)
Student.prototype.studying = function () {
console.log('studying~')
}
let stu = new Student('student', 18, ['xiaoming'], 111, 100)
console.log(stu)
stu.studying()
stu.running()
console.log(stu.constructor.name)
这个方法比较成熟 前面继承方式弊端基本解决 只调用一次父类构造函数 Child可以向Parent传参 父类方法可以复用 父类的引用属性不会被共享 ES6中的类继承extends也是使用寄生组合继承原理
文章更多作为个人学习,有错欢迎指出,也希望对你有所帮助