Skip to content

JavaScript 三种常用的继承方式 #4

@JuniorTour

Description

@JuniorTour

继承是面向对象语言的一大特性。通过继承,可以让一种类型拥有其他类型的属性和方法。
JavaScript也有自己实现继承的方式,本文将参考《JavaScript高级程序设计》总结并介绍3种常用常见的继承方式。

0.原型链继承:

这种方法通过将子类的原型重写为父类的实例实现继承。

核心逻辑:Bird.prototype = new Animal()

//父类:动物
function Animal() {
    this.isLife = true
    this.characteristic = ['living','eat']
}
Animal.prototype.earnLiving=function () {
    console.log('Animal need earn its living.')
}

//子类:鸟类
function Bird() {
    this.canFly = true
}
/*原型链继承:通过将子类的原型重写为父类的实例实现继承。*/
Bird.prototype = new Animal()
/*继承后,子类就会拥有父类的属性和方法,
这也是继承和原型链的目的。*/
Bird.prototype.flying = function (){
    console.log('I am bird, I am flying.')
}

let sparrow = new Bird()
/*继承后,子类就会拥有父类的属性和方法,
这也是继承/原型链的目的。*/
sparrow.earnLiving()
console.log('After inherit sparrow.isLife = '+sparrow.isLife)
sparrow.flying()

/*原型链继承的弊端:公用引用类型问题*/
sparrow.characteristic.push('grayFeather')  // ["living", "eat", "grayFeather"]

let eagle = new Bird()
/*公用引用类型问题是指,子类的所有实例都会共享原型链上的引用类型属性:
eagle也会继承sparrow的grayFeather,这往往不是想要的结果。*/
console.log(eagle.characteristic)   // ["living", "eat", "grayFeather"]

// 只有`引用类型`会被共享, 基础类型(String, Number等)并不会
  • 缺点:
    • 公用引用类型问题:子类的所有实例都会共享原型链上的引用类型属性
    • 不能在继承时传递参数。

1. 借用构造函数继承

这种方法是通过借调父类的构造函数,从而在子类实例的环境下,让子类的实例独立地继承父类的属性和方法,能够避免原型链继承所导致的公用引用类型问题和不能在继承时传递参数的缺点。

核心逻辑:Animal.call(this,'Bird')

//父类:动物
function Animal(type) {
    this.type = type
    this.isLife = true
    this.characteristic = ['living','eat']
}

//子类:鸟类
function Bird() {
    /*借用构造函数继承:通过在Bird类实例的环境下
    调用Animal构造函数来实现实例继承属性和方法。
    同时可以结局原型链继承所导致的公用引用类型问题,
    还能实现传递参数!*/
    Animal.call(this,'Bird')
    this.canFly = true
}
let sparrow = new Bird()

sparrow.characteristic.push('grayFeather')
console.log(sparrow.characteristic) // ["living", "eat", "grayFeather"]
console.log(sparrow.type)   //可以在继承时传递参数

let eagle = new Bird()
/*原型链继承的弊端不会在此出现,
sparrow和eagle都有各自独立的引用类型-数组属性。*/
console.log(eagle.characteristic)   //["living", "eat"]

2. 组合继承

这种方式将原型链继承和借用构造函数继承组合在一起,取长补短,既通过在原型上定义方法实现函数复用,又保证每个实例都有自己的属性,避免了公用引用类型问题。是JavaScript中最常用的一种继承方式。

核心逻辑:

  • Animal.call(this,'Bird')
  • Bird.prototype = new Animal()
//父类:动物
function Animal(type) {
    this.type = type
    this.isLife = true
    this.characteristic = ['living','eat']
}
//原型链继承:
Animal.prototype.earnLiving = function () {
    console.log('Animal need earn its living.')
}

//子类:鸟类
function Bird() {
    //借用构造函数继承
    Animal.call(this,'Bird')
    this.canFly = true
}
//原型链继承:
Bird.prototype = new Animal()

Bird.prototype.flying = function (){
    console.log('I am bird, I am flying.')
}

let sparrow = new Bird()

sparrow.characteristic.push('grayFeather')
console.log(sparrow.characteristic) // ["living", "eat", "grayFeather"]
console.log(sparrow.type)   //可以在继承时传递参数
//继承了父类方法
sparrow.flying()

let eagle = new Bird()
/*解决了原型链继承的公用引用类型弊端,
sparrow和eagle都有各自独立的引用类型-数组属性。*/
console.log(eagle.characteristic)   //["living", "eat"]
//继承了父类方法
eagle.earnLiving()

3. 识别实例和原型

原生JavaScript有很多方法可以用来检测原型和实例之间的关系,比如:

  • Object.prototype.isPrototypeOf({})
  • Object.getPrototypeOf(anInstance) === Object.prototype
  • anInstance instanceof anConstructor

4. 特殊情况

  1. 重写原型
function A() {}
A.prototype.n=1

var b = new A()
A.prototype={ // 完全重写, 替代掉A的原型
    n:2,
    m:3
}

var c = new A()
console.log(b.n)  // 1
console.log(b.m) // undefined
// 因为 b 用的还是未重写之前的原型
console.log(c.n)  // 2
console.log(c.m) // 3
  1. JavaScript的多态

本人家里养了一只鸡,一只鸭。当主人向他们发出‘叫’的命令时。鸭子会嘎嘎的叫,而鸡会咯咯的叫。转化成代码形式如下

// Src: https://segmentfault.com/q/1010000003056336
// 非多态代码示例
var makeSound = function(animal) {
    if(animal instanceof Duck) {
        console.log('嘎嘎嘎');
    } else if (animal instanceof Chicken) {
        console.log('咯咯咯');
    }
}
var Duck = function(){}
var Chiken = function() {};
makeSound(new Chicken());
makeSound(new Duck());

// 多态的代码示例
var makeSound = function(animal) {
    animal.sound();
}

var Duck = function(){}
Duck.prototype.sound = function() {
    console.log('嘎嘎嘎')
}
var Chiken = function() {};
Chiken.prototype.sound = function() {
    console.log('咯咯咯')
}

makeSound(new Chicken());
makeSound(new Duck());

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions