从零开始学_JavaScript_系列(64)——class的继承(1)基本概念、继承构造函数和class

1、class继承概述

1.1、所谓继承

继承这个概念还需要解释么?好吧,我解释一下:

简单粗暴的解释继承,就是指:

  1. 我定义一个类A,他自带一些方法;
  2. 然后我定义一个类B,他有一些方法是独特的,但他还有一些方法是类A已有的,那么如果让类B再重复定义这些类A已有的方法,显然是低效的写法;
  3. 这种让类B可以使用类A的方法的做法,就是继承;
  4. 传统解决办法是创建一个类A的实例,然后让类B指向这个创建的实例,从而利用原型链来实现;
  5. 而class相对传统方法来说,更简单,他的写法类似c++中的继承,利用关键字extends来实现。

以上继承中,类A就是父类,类B就是子类。

1.2、class的继承

子类 extends 父类 {子类的方法}

继承的语法如上,关键字是extends(记得有s)代码如下:

class Foo {
    logFoo() {
        console.log("foo")
    }
}

class Bar extends Foo {
    logBar() {
        console.log("bar")
    }
}
let p = new Bar()
p.logBar()  // "bar"
p.logFoo()  // "foo"
1.3、继承与构造函数

class在继承时,子类的构造函数constructor可写可不写。

假如子类有自己的构造函数的话,那么必须在子类构造函数里执行super()函数,并且需要在调用this之前,不然会报错。

super( 传给父类构造函数的参数 )

class Foo {
    constructor(a) {
        console.log(a)
    }
}

class Bar extends Foo {
    constructor(b) {
        console.log(b)
        super(b + 1)
    }
}

let p = new Bar(1)
// 1
// 2

输出内容里:

  1. 1 是创建Bar的实例时,作为参数传给Bar的构造函数的;
  2. 2 是通过super传给Foo的构造函数的参数;

另外,如果要在构造函数里使用this,那么必须先调用super()

class Foo {
}

class Bar extends Foo {
    constructor() {
        // this.a = 1  这里会报错
        super()
        this.a = 1  // 在使用this之前,必须先调用super来调用父类的构造函数
    }
}

至于为什么,贴一段引自阮一峰的解释

ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
ES6 的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。

简单来说,就是es5先子类后父类,es6先父类后子类。

1.4、父类和子类的关系

首先,我们需要分析一下类的方法和类本身的关系。

因为class目前的实现是依靠原型链的,是原型链的一个语法糖,因此class的方法都可以在原型链上找到。

class Foo {
    foo() {
    }
}
typeof Foo.prototype.foo    // "function"

说明类的方法是在他的原型链(prototype)上的。

其次,当子类继承父类的时候,父类位于原型链上的什么位置呢?我们可以通过代码测试得知

class Foo {
    foo() {
    }
}

class Bar extends Foo {
    bar() {
    }
}

Bar.__proto__ === Foo;  // true
Bar.__proto__.prototype === Foo.prototype;    //true
Bar.prototype.__proto__ === Foo.prototype;  // true

前两个表达式解释了类的静态方法

  1. 类Bar的__proto__属性指向类Foo,因此当类Foo有静态方法时,Bar可以直接调用(靠__proto__);
  2. 所以自然Bar.__proto__.prototype === Foo.prototype了;


但第三个表达式如何解释,子类在继承父类是如何实现的?

  1. 而我们知道,当构造函数通过new来生成实例时,构造函数的prototype属性将被自动转为__proto__作为实例的原型链(只有对象在执行方法时,会顺着__proto__去找对象本身没有的方法);
  2. 而从上面类和方法的示例里,我们可以知道类的方法位于类的prototype属性上;
  3. 因此如果要实现继承,那么父类的方法必然位于子类的prototype上(不然子类的实例找不到父类的方法);
  4. 但是,显然父类的方法,不能直接挂载在子类的prototype属性上,虽然在实际使用时,只要控制子类方法后写入该属性即可(后写入会覆盖先写入的);
  5. 但这样明显不是原型链继承的风格(不是链式继承)。
  6. 那么如何让子类的实例可以调用父类的方法呢,答案很简单,对象在调用方法时,如果找不到对应的方法,那么会沿着__proto__属性一直往上找。
  7. 因此只需要把父类prototype属性挂在子类的prototype.__proto__上即可(即实例的__proto__.__proto__上);

因此可以推导出第三个表达式。

更多的在下面分析。

2、获取class的父类

Object.setPrototypeOf(obj, prototype)
Object.getPrototypeOf(object)

效果:

  1. 设置当前对象的原型链;
  2. 获取当前对象的原型链;

简单来说,用我们通常的理解:

  1. 第一个相当于foo.__proto__ = bar
  2. 第二个相当于foo.__proto__

之所以有这两个方法的原因,在于__proto__属性并非是标准的方法,而是浏览器厂商都这么干,于是相当于约定俗成的做法,但实质上来说,并不是标准做法,所以不是最好的。

于是,我们也可以通过这个方法来获取原型链,或者证明类A是不是类B的父类

class Foo {
    foo() {
    }
}

class Bar extends Foo {
    bar() {
    }
}
Object.getPrototypeOf(Bar) === Foo  // true

但这个方法也有一个缺点,只能获取到直接父类,如果是父类的父类(祖先),那便无法获得

如代码:

class Foo {
    foo() {
    }
}

class Bar extends Foo {
    bar() {
    }
}

class Baz extends Bar {

}

Object.getPrototypeOf(Baz) === Bar  // true
Object.getPrototypeOf(Baz) === Foo  // false
Object.getPrototypeOf(Object.getPrototypeOf(Baz)) === Foo   // true

既然能获取到类的原型链,那么能不能通过 Object.setPrototypeOf(obj, prototype) 来设置原型链,替代extends这种方式来继承呢?

答案是否定的。先根据class的继承链上代码:

class Foo {
    constructor() {
        console.log('Foo constructor')
    }
    foo() {
        console.log('foo')
    }
}

class Bar {
    constructor() {
        console.log('Bar constructor')
    }

    bar() {
        console.log('bar')
    }
}

Object.setPrototypeOf(Bar, Foo)
Object.setPrototypeOf(Bar.prototype, Foo.prototype)
let p = new Bar()
// Bar constructor
p.foo() // foo
p.bar() // bar

虽然从表面看,Bar的实例既可以调Bar的方法,也可以调Foo的方法,看似很正常。

但是在new Bar()这一步。只调用了Bar的构造函数,并没有调用Foo的构造函数。

虽然看似在Bar的constructor函数里,以下表达式的值是true

this.__proto__.__proto__.constructor === Foo    // true

但是Foo的构造函数是没办法直接调用执行的

this.__proto__.__proto__.constructor    // Uncaught TypeError: Class constructor Foo cannot be invoked without 'new'

因此,就算设法实现,但过程也是十分麻烦的,并且不合标准的class使用方法。不予考虑。

3、class继承普通构造函数

3.1、传统构造函数与class

在【2】中提过,class不通过extends关键字来继承另外一个class是十分麻烦的事情。因此我们应该使用extends来继承class。

但是若假如我们现有一个普通的构造函数,他有一些直接挂载在类本身的方法,也有一些挂载在prototype上的方法,还有一个构造函数。

如以下代码,是一个标准的传统构造函数:

function Foo() {
    console.log('Foo constructor')
}
Foo.staticFoo = function () {
    console.log("static foo")
}
Foo.prototype.foo = function () {
    console.log('foo')
}

如果用class来实现这个构造函数,就是:

class Foo {
    constructor() {
        console.log('Foo constructor')
    }

    foo() {
        console.log('foo')
    }

    static staticFoo() {
        console.log("static foo")
    }
}
3.2、class的继承

如果我现在根据需要,想要继承父类Foo,假如是父类是用class声明的,那么很简单,方法如下:

class Bar extends Foo {
    constructor() {
        super()
        console.log('Bar constructor')
    }

    bar() {
        console.log('bar')
    }
}
let p = new Bar()
// Foo constructor
// Bar constructor
p.foo() // foo
p.bar() // bar  
Bar.staticFoo() // static foo
p.staticFoo()   // Uncaught TypeError: p.staticFoo is not a function

通过关键字super()来调用父类的构造函数(先别管super是什么,下面说)。

3.3、传统构造函数的继承

那么如果是一个普通构造函数(我们第一次列出来的那个),Bar该如何继承他呢?

两种方法:

1、extends关键字继承法:

很简单,就像继承普通class那样继承就行了。如代码:

function Foo() {
    console.log('Foo constructor')
}
Foo.staticFoo = function () {
    console.log("static foo")
}
Foo.prototype.foo = function () {
    console.log('foo')
}

class Bar extends Foo {
    constructor() {
        super()
        console.log('Bar constructor')
    }

    bar() {
        console.log('bar')
    }
}

let p = new Bar()
// Foo constructor
// Bar constructor
p.foo() // foo
p.bar() // bar
Bar.staticFoo() // static foo
p.staticFoo()   // Uncaught TypeError: p.staticFoo is not a function

2、但假如,我头很铁,就是不想用extends关键字,怎么办?

当然也是可以的,核心在于以下两行等式:

Bar.__proto__ === Foo;  // true
Bar.prototype.__proto__ === Foo.prototype;  // true

即父类本身成为子类的原型(第一行等式),以及子类原型链的原型,指向父类的原型链(第二行等式)

这是两条原型链。

因此在class继承普通构造函数的时候,也需要做出这两条原型链,除此之外,还要调用原方法的构造函数。

如代码:

function Foo() {
    console.log('Foo constructor')
}
Foo.staticFoo = function () {
    console.log("static foo")
}
Foo.prototype.foo = function () {
    console.log('foo')
}

class Bar {
    constructor() {
         // 核心:构造函数
        Foo.prototype.constructor.call(this)
        console.log('Bar constructor')
    }

    bar() {
        console.log('bar')
    }
}
// 核心:两条原型链
Bar.__proto__ = Foo
Bar.prototype.__proto__ = Foo.prototype

let p = new Bar()
// Foo constructor
// Bar constructor
p.foo() // foo
p.bar() // bar
Bar.staticFoo() // static foo
p.staticFoo()   // Uncaught TypeError: p.staticFoo is not a function

因此便能很好的完成继承的效果了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值