从零开始学_JavaScript_系列(64)——class的继承(1)基本概念、继承构造函数和class
来源:互联网 发布:php通过ip获取经纬度 编辑:程序博客网 时间:2024/05/22 03:27
1、class继承概述
1.1、所谓继承
继承这个概念还需要解释么?好吧,我解释一下:
简单粗暴的解释继承,就是指:
- 我定义一个类A,他自带一些方法;
- 然后我定义一个类B,他有一些方法是独特的,但他还有一些方法是类A已有的,那么如果让类B再重复定义这些类A已有的方法,显然是低效的写法;
- 这种让类B可以使用类A的方法的做法,就是继承;
- 传统解决办法是创建一个类A的实例,然后让类B指向这个创建的实例,从而利用原型链来实现;
- 而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
是创建Bar的实例时,作为参数传给Bar的构造函数的;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; // trueBar.__proto__.prototype === Foo.prototype; //trueBar.prototype.__proto__ === Foo.prototype; // true
前两个表达式解释了类的静态方法:
- 类Bar的__proto__属性指向类Foo,因此当类Foo有静态方法时,Bar可以直接调用(靠
__proto__
); - 所以自然
Bar.__proto__.prototype === Foo.prototype
了;
但第三个表达式如何解释,子类在继承父类是如何实现的?
- 而我们知道,当构造函数通过new来生成实例时,构造函数的
prototype
属性将被自动转为__proto__
作为实例的原型链(只有对象在执行方法时,会顺着__proto__
去找对象本身没有的方法); - 而从上面类和方法的示例里,我们可以知道类的方法位于类的
prototype
属性上; - 因此如果要实现继承,那么父类的方法必然位于子类的
prototype
上(不然子类的实例找不到父类的方法); - 但是,显然父类的方法,不能直接挂载在子类的
prototype
属性上,虽然在实际使用时,只要控制子类方法后写入该属性即可(后写入会覆盖先写入的); - 但这样明显不是原型链继承的风格(不是链式继承)。
- 那么如何让子类的实例可以调用父类的方法呢,答案很简单,对象在调用方法时,如果找不到对应的方法,那么会沿着
__proto__
属性一直往上找。 - 因此只需要把父类
prototype
属性挂在子类的prototype.__proto__
上即可(即实例的__proto__.__proto__
上);
因此可以推导出第三个表达式。
更多的在下面分析。
2、获取class的父类
Object.setPrototypeOf(obj, prototype)
Object.getPrototypeOf(object)
效果:
- 设置当前对象的原型链;
- 获取当前对象的原型链;
简单来说,用我们通常的理解:
- 第一个相当于
foo.__proto__ = bar
; - 第二个相当于
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 // trueObject.getPrototypeOf(Baz) === Foo // falseObject.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 constructorp.foo() // foop.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 constructorp.foo() // foop.bar() // bar Bar.staticFoo() // static foop.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 constructorp.foo() // foop.bar() // barBar.staticFoo() // static foop.staticFoo() // Uncaught TypeError: p.staticFoo is not a function
2、但假如,我头很铁,就是不想用extends
关键字,怎么办?
当然也是可以的,核心在于以下两行等式:
Bar.__proto__ === Foo; // trueBar.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__ = FooBar.prototype.__proto__ = Foo.prototypelet p = new Bar()// Foo constructor// Bar constructorp.foo() // foop.bar() // barBar.staticFoo() // static foop.staticFoo() // Uncaught TypeError: p.staticFoo is not a function
因此便能很好的完成继承的效果了。
- 从零开始学_JavaScript_系列(64)——class的继承(1)基本概念、继承构造函数和class
- 从零开始学_JavaScript_系列(65)——class的继承(2)super、extends与多重继承
- 从零开始学_JavaScript_系列(60)——class(1)基本概念
- 从零开始学_JavaScript_系列(62)——class(3)setter和getter、Generator、async函数
- 从零开始学_JavaScript_系列(53)——Generator函数(1)基本概念和示例
- 从零开始学_JavaScript_系列(56)——Generator函数(4)简写,this与继承
- 从零开始学_JavaScript_系列(63)——class(4)静态方法和new.target
- 从零开始学_JavaScript_系列(18)——dojo(7)(dojo中类的继承)
- 从零开始学_JavaScript_系列(35)——继承与遍历的对照表
- 从零开始学_JavaScript_系列(42)——简述js的八种继承方式
- 从零开始学_JavaScript_系列(61)——class(2)私有方法、this
- 从零开始学_JavaScript_系列(58)——Thunk函数
- 从零开始学_JavaScript_系列(59)——async函数
- 从零开始学_JavaScript_系列(57)——Generator函数(5)状态机与函数的应用
- 从零开始学_JavaScript_系列(54)——Generator函数(2)简单应用、throw和return
- 从零开始学_JavaScript_系列(27)——myblog的优化【1】样式表分离、localStorage
- 从零开始学_JavaScript_系列(30)——NodeList
- 从零开始学_JavaScript_系列(32)——事件广播
- Eigen demo与文件读写 汇总
- 30岁程序员困境:转行or跳槽?如何做才不会被替代?
- 经典游戏——贪吃蛇
- 复杂json转list
- ThinkPHP数据分页带入查询条件
- 从零开始学_JavaScript_系列(64)——class的继承(1)基本概念、继承构造函数和class
- MODBUS-寄存器与功能码学习
- 黄海高程和海拔高程之间的转换
- Java中常用数据结构
- android常见二维码,普通二维码,带Logo的二维码
- Android框架之路——Banner实现轮播图(RecyclerView添加Header)
- Transitions-Everywhere
- Dom4j解析XML文件子节点
- TP5分页使用方法,在使用paginate(10)后无法foreach得到的数据集合