javascript实现继承的几种方式,及其优缺点总结

蛰伏已久 蛰伏已久 2019-06-15

面向对象3个基本特征:封装、多态和继承,继承可以使子类具有父类的属性和方法。

在javascript中,通过原型链来实现继承,有以下几种方法。

子类的原型对象-类式继承

//声明父类
function SuperClass() {
    //父类属性
    this.superValue = "super"
}
//为父类添加共有方法
SuperClass.prototype.showSuperValue = function () {
    console.log(this.superValue)
}

//声明子类
function SubClass() {
    this.subValue = 'suber'
}

//通过将原型对象设置为父类的实例,实现继承
SubClass.prototype = new SuperClass()
//上述赋值操作会覆盖构造函数,重新设置一下
SubClass.prototype.constructor = SubClass
//为子类添加共有方法
SubClass.prototype.showSubValue = function () {
    console.log(this.subValue)
}

//实例化
let sub1 = new SubClass()
console.log(sub1.superValue)  
console.log(sub1.subValue)    
sub1.showSuperValue()
sub1.showSubValue()

我们在实例化一个对象的时候,新创建的对象会赋值构造函数中的属性和方法,并且将__proto__指向了类的原型对象。

实例化sub1时,会复制subValue属性,因此subValue是sub1的自有属性,sub1的__proto__指向了SubClass.prototype。

而SubClass.prototype由SuperClass实例化而来,因此它复制了SuperClass的属性superValue,还有后来添加的showSubValue方法。

 SubClass.prototype也有__proto__属性,指向了SuperClass.prototype,而SuperClass.prototype只有一个showSuperValue方法

console.log(sub1.hasOwnProperty('subValue'))     //true
console.log(sub1.hasOwnProperty('superValue'))   //false
console.log(sub1.__proto__)                     
/*
  SubClass {
      superValue: 'super',
      showSubValue: [Function],
      constructor: [Function: SubClass] }
  */
console.log(sub1.__proto__.__proto__)
//SuperClass { showSuperValue: [Function] }

当访问一个实例的属性时,先在该实例的自有属性中查找,如果找不到就到__proto__属性中查找,如果还找不到继续往上找,直到__proto__为null,都找不到则说明未定义该属性。

当修改一个实例的属性时,如果不是该实例的自有属性,则添加一个自有属性,如

let sub1 = new SubClass()
console.log(sub1.hasOwnProperty('superValue'))  //false
sub1.superValue = 'sub1'                        //修改继承的属性,变成了添加自有同名属性
console.log(sub1.hasOwnProperty('superValue'))  //true

实例会复制类的构造函数中的属性,但是会共享父类的属性,如果父类属性为引用类型,则会出问题。

function SuperClass() {
    this.superValue = ["super"] //父类属性为引用类型
}


function SubClass() {
}

SubClass.prototype = new SuperClass()

let sub1 = new SubClass()
let sub2 = new SubClass()

sub1.superValue.push('text')

console.log(sub1.superValue)   //["super","text"]
console.log(sub2.superValue)   //["super","text"]

类式继承缺点:

  1. 如果父类属性的共有属性是引用类型,会被子类中所有实例共有,一个子类实例修改这个共有属性,会影响其他实例

  2. 由于子类实现的继承是靠其原型protype对父类的实例化实现,因此没法向父类传递参数

创建即继承-构造函数继承

//声明父类
function SuperClass(name) {
    //引用类型共有属性
    this.superValue = ["super"]
    this.name = name
}

//父类的原型方法
SuperClass.prototype.showSuperValue=function () {
    console.log(this.superValue)
}

//声明子类
function SubClass(name) {
     //继承父类
    SuperClass.call(this,name)
}

let sub1 = new SubClass("subname1")
let sub2 = new SubClass("subname2")
sub1.superValue.push('test')


console.log(sub1.name)         //subname1
console.log(sub1.superValue)   //[ 'super', 'test' ]
console.log(sub2.superValue)   //[ 'super' ]
sub1.showSuperValue()          //报错,未定义该方法

构造函数继承的核心是在构造函数中通过call方法,调用父类的构造函数,传递this和参数。但是由于未涉及原型protorype,所以父类的方法没有得到继承。

将优点集合-组合继承

将上述两种方式进行组合,集合两种方式的优点。

function SuperClass(name) {
    this.superValue = ["super"]
    this.name = name
}

SuperClass.prototype.showSuperValue=function () {
    console.log(this.superValue)
}

function SubClass(name) {
    //构造函数继承,继承父类的属性
    SuperClass.call(this,name)
}
//类式继承,子类原型继承父类
SubClass.prototype = new SuperClass()

let sub1 = new SubClass("subname1")
let sub2 = new SubClass("subname2")
sub1.superValue.push('test')


console.log(sub1.name)
console.log(sub1.superValue)
console.log(sub2.superValue)
sub1.showSuperValue()   //[ 'super', 'test' ]

通过这种组合,子类实例修改父类继承下来的引用类型属性superValue,不会影响其他实例;

并且子类实例化过程中,又能将参数传递到父类的构造函数中,如name。

但是还是存在一个问题,子类构造函数中调用了一次父类的构造函数,在设置子类原型时,又调用了一次,共调用了两次。

洁净的继承者-原型式继承

2006年格拉斯•克罗克福德发布一篇《JavaScript 中原型式继承》的文字,他的观点是借助原型prototype可以根据已有的对象,创建一个新对象,同时不必创建新的自定义对象类型。

function inheritObject(o) {
    function F() {}
    F.prototype = o
    return new F()
}

封装了一个继承函数,通过类式继承的方式继承传入的参数对象o

function inheritObject(o) {
    function F() {}
    F.prototype = o
    return new F()
}

let book = {
    name:'js book',
    alikeBook:['css book','html book']
}

let book1 = inheritObject(book)
book1.name = 'canvas book'
book1.alikeBook.push('svg book')
let book2 = inheritObject(book)
book2.name = 'php book'

console.log(book1.name)        //canvas book
console.log(book1.alikeBook)   //[ 'css book', 'html book', 'svg book' ]

console.log(book2.name)        //php book
console.log(book2.alikeBook)   //[ 'css book', 'html book', 'svg book' ]

跟类式继承一样,修改父类的引用属性,会影响其他实例对象。

寄生式继承

function inheritObject(o) {
    function F() {}
    F.prototype = o
    return new F()
}

let book = {
    name:'js book',
    alikeBook:['css book','html book']
}


function createBook(obj) {
    let o = inheritObject(obj)
    o.getName = function () {
        console.log(this.name)
    }

    return o
}

let book1 = createBook(book)

console.log(book1.name)

book1.getName()

寄生式继承,对原型继承进行了二次封装,在封装中对继承的对象进行了拓展,这样新创建的对象不仅拥有父类的属性和方法,而且还添加新的属性和方法。

寄生组合继承

function inheritObject(o) {
    function F() {}
    F.prototype = o
    return new F()
}

function inheritPrototype(subClass,superClass) {
    let p = inheritObject(superClass.prototype)
    p.constructor = subClass
    subClass.prototype = p
}

封装一个函数,来实现子类原型继承于父类原型,这里没有使用构造函数,完整的继承如下。

function inheritObject(o) {
    function F() {}
    F.prototype = o
    return new F()
}

function inheritPrototype(subClass,superClass) {
    let p = inheritObject(superClass.prototype)
    p.constructor = subClass
    subClass.prototype = p
}

function SuperClass(name) {
    this.name = name
}

SuperClass.prototype.getName = function () {
    console.log(this.name)
}


function SubClass(name,color) {
    //构造函数继承
    SuperClass.call(this,name)
    this.color = color
}

//寄生式继承父类原型
inheritPrototype(SubClass,SuperClass)

SubClass.prototype.getColor = function () {
    console.log(this.color)
}

let instance = new SubClass('wang','green')
instance.getName()
instance.getColor()

通过这种方式,可以在构造函数中像父类传递参数,而且父类构造函数只执行了一次。


分享到

点赞(0)