面向对象编程的三大特征:封装,继承,多态
js中继承的本质是一种委托机制,对象可以将需要的属性和方法委托给原型,这样多个对象可以共享一个原型上的属性和方法
原型链继承
子类构造函数的原型为父类构造函数的实例对象Son.prototype = new Father()
缺点:
(1)父类所有属性都会被子类共享,更改一个子类的引用属性,其他子类也会受影响
(2)子类型不能给父类型构造函数传参
function Parent() {
this.name = '父亲';
this.info = {
age: 54,
sex: 'male'
}
}
Parent.prototype.start = function () {
console.log(`Starting the ${this.name}`, this.info)
}
function Son(age) {
this.age = 20
}
Son.prototype = new Parent()
Son.prototype.constructor = Son
let son1 = new Son()
son1.info.gender = '男'
son1.start() // { age: 54, sex: 'male', gender: '男' }
let son2 = new Son()
son2.start() // { age: 54, sex: 'male', gender: '男' }
构造函数继承
使用call()和apply()方法,在子类构造函数中,调用父类构造函数(在子类Son里面执行Person.call(this))
缺点:
(1)只继承了父类构造函数的属性,没有继承父类原型上的属性和方法
(2)无法实现函数复用,如果父类构造函数里面只有一个方法,会导致每个子类实例上面有相同的方法
function Father(name) {
this.name = name,
this.say = function () {
console.log('this say')
}
}
Father.prototype.showName = function () {
console.log(this.name)
}
function Son(name, age) {
Father.call(this,name) // 在Son中借用了Father函数,只继承了父类构造函数的属性,没有继承父类原型的属性
this.age = age
}
let s = new Son('刘刘', 20)
let s2 = new Son('刘3', 19)
console.log(s.name)
console.log(s.showName) // undefined , 没有继承父类原型上的属性和方法
// 每个子类实例上面有相同的方法
s2.say()
s.say()
组合继承
构造函数继承和原型链继承(普通属性使用构造函数继承,函数使用原型链继承)
缺点:
使用组合继承时,父类构造函数会被调用两次,子类实例对象与子类的原型上会有相同的方法与属性,浪费内存
function Person(name) {
this.name = name
this.say = function() {
console.log('hello World')
}
}
Person.prototype.showName = function() {
console.log(this.name)
}
function Child(name, age) {
Person.call(this,name) // 构造函数继承
this.age = age
}
// 原型链继承
Child.prototype = new Person() // Child实例的原型上,会有同样的属性,父类构造函数相当于调用了两次
Child.prototype.constructor = Child // 将Child的原型的构造函数指向Child,否则Child实例的consturctor指向Person
Child.prototype.showAge = function() {
console.log(this.age)
}
let p = new Child('jj88', 20)
p.say()
p.showName() // 继承了父类构造函数的原型方法
p.showAge()
原型式继承
创建一个函数,将要继承的对象通过参数传递给这个函数,最终返回一个对象,它的隐式原型指向传入的对象
缺点:
(1)只能继承父类函数原型对象上的属性和方法,不能继承构造函数的属性
(2)子类无法给父类传参
function createObj(obj) {
function F(){}
F.prototype = obj
F.prototype.constructor = F
return new F
}
function Father() {
this.name = '7788'
}
Father.prototype.showName = function() {
console.log(this.name)
}
const Son = createObj(Father.prototype)
Son.showName() // undefined 继承了原型上的方法,但是没有继承构造函数里的name属性
寄生式继承
在原型式继承的函数里,给继承的对象上添加属性和方法,增强这个对象
缺点:
(1) 无法给父类构造函数传参
(2)多个实例共用同一个原型,属性会相互影响
function createObj(obj) {
function F(){}
F.prototype = obj
F.prototype.constructor = F
F.prototype.age = 20
F.prototype.showAge = function() {
console.log(this.age)
}
return new F
}
function Father() {
this.name = '7788'
}
Father.prototype.showName = function() {
console.log(this.name)
}
const Son2 = createObj(Father.prototype)
Son2.showName() // undefined 继承了原型上的方法,但是没有继承构造函数里的name属性
Son2.showAge() // 20
寄生组合继承
寄生式继承+组合继承,解决了组合继承中重复调用父类构造函数的问题,通过构造函数来继承父类属性,通过原型链的方式来继承父类的方法
function Father(name) {
this.name = name
this.say = function() {
console.log('hello World')
}
}
Father.prototype.showName = function() {
console.log(this.name)
}
function Son(name, age) {
Father.call(this,name) // 构造函数继承
this.age = age
}
// Object.create()返回一个对象,它的隐式原型指向传入的对象
Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son
const c1 = new Son('ll', 20)
console.log(c1) // { name: 'll', say: [Function (anonymous)], age: 20 }
console.log(c1.showName()) // ll
ES6的extends关键字实现继承
extends关键词用于实现类的继承。当一个类通过extends关键字继承另一个类时,它实际上在原型链上建立了与父类的连接。这意味着子类的原型对象指向了父类的原型对象,从而继承了父类的属性和方法
class Father{
constructor(name) {
this.name = name
}
showName() {
console.log(this.name)
}
}
class Son extends Father { // 子类通过extends继承父类
constructor(name,age){
super(name) // 调用父类里的constructor函数,等同于Father.call(this,name)
this.age=age
}
showAge() {
console.log(this.age)
}
}
const son = new Son('ss', 20)
son.showName() // ss
son.showAge() // 20
extends关键字的实现逻辑:
(1)建立原型链关系:子类通过extends关键字继承父类时,子类的原型对象会指向父类的原型对象,从而建立了原型链关系。这样子类就可以访问父类原型对象中定义的属性和方法
(2)调用父类构造函数:子类的构造函数中可以通过super()方法调用父类的构造函数,并传递参数。这样可以在子类中初始化继承自父类的属性
(3)子类特有方法:子类可以定义自己特有的方法,这些方法会被添加到子类的原型对象上
ES6中类的继承就是寄生组合继承