ECMAScript 6 笔记(25)- Class 的继承

原创 乘风逐月 随笔 ES6 69阅读 2019-01-17 11:26:09 举报

一、extends 关键字

Class 可以通过 extends 关键字实现继承,这比 ES5 的通过修改原型链实现继承要清晰和方便。

上例中,Bar 通过 extends 关键字,继承了 Foo 类的所有属性和方法,包括父类的静态方法也会被子类继承。

二、Object.getPrototypeOf()

Object.getPrototypeOf 方法可以用来从子类上获取父类。

因此可以用来判断一个类是否继承了另一个类。

三、super 关键字

super 关键字,既可以当做函数使用,也可以当做对象使用。在这两种情况下,它的用法完全不同。

1.super 作为函数使用

super 作为构造函数调用时,代表父类的构造函数。 ES6 要求,子类必须在 constructor 方法中执行一次 super 函数。

上例中,子类 B 的 super(),代表调用父类的构造函数。这是必须执行的,否则 JavaScript 引擎会报错。这是因为子类自己的 this 对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后加上子类自己的实例属性和方法。 如果不调用 super 方法,子类就得不到 this 对象。

上例中,B 继承了 A,但是它的构造函数中没有调用 super 方法,导致新建实例报错。

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

注意:
(1)子类都有 constructor 方法,如果子类没有显示定义 constructor 方法,这个方法会被默认添加。

(2)在子类的构造函数中,只有调用了 super 之后,才可以使用 this 关键字,否则会报错。这是因为子类实例的构建,基于父类实例。

(3) super 虽然代表了父类的构造函数,但是返回的是子类的实例。

上例中,new.target 指向当前正在执行的函数。可以看到,在 super() 执行时,它指向子类 B 的构造函数,而不是父类 A 的构造函数,即 super() 内部的 this 指向 B。

(4)作为函数时, super() 只能用在子类的构造函数之中,在其他地方会报错。

2.super 作为对象

super 作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

注意:

(1) super 指向父类的原型对象,所以定义在父类上例上的方法或属性,是无法通过 super 调用的。定义在父类的原型对象上的属性 super 可以取到。

(2)ES6 规定,在子类普通方法中通过 super 调用父类的方法时,方法内部的 this 指向当前的子类实例。

上例中,super.print() 虽然调用的是 A.prototype.print(),但是 A.prototype.print() 内部的 this 指向子类 B 的实例,所以输出的是 2,即实际执行的是 super.print.call(this)。

由于 this 指向子类实例,所以如果通过 super 对某个属性赋值,这时 super 就是 this ,赋值的属性就会变成子类实例的属性。

上例中,super.x = 3,相等于对 this.x 赋值为3。而读取 super.x 时,读取的是 A.prototype.x,所以返回 undefined。

(4)在子类的静态方法中通过 super 调用父类的方法时,方法内部的 this 指向当前的子类,而不是子类的实例。

上例中,静态方法 B.m 里面,super.print 指向父类的静态方法。这个方法里面的 this 指向的是 B,而不是 B 的实例。

(5)使用 super 时,必须显示指定是作为函数,还是作为对象使用,否则会报错。

(6)由于对象总是继承其他对象的,所以可以在任意一个对象中使用 super 关键字。

四、类的 prototype 属性和 -proto- 属性

大多数浏览器的 ES5 实现中,每一个对象都有 -proto- 属性,指向对应的构造函数的 prototype 属性。Class 作为构造函数的语法糖,同时有 prototype 属性和 -proto- 属性,因此存在两条继承链。
(1)子类的 -proto- 属性,表示构造函数的继承,总是指向父类。
(2)子类 prototype 属性的 -proto- 属性,表示方法的继承,总是指向父类的 prototype 属性。

这样的结果是因为,类的继承是按照下面的模式实现的。

注意:
extends 关键字后面可以跟多种类型的值。

上例中的 A ,只要是一个有 prototype 属性的函数就能被 B 继承。由于函数都有 prototype 属性,因此 A 可以是任意函数。
(1)子类继承 Object

这种情况下,A 就是构造函数 Object 的复制,A 的实例就是 Object 的实例。
(2)不存在任何继承

这种情况下,A 作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承 Function.prototype。但是,A 调用后返回一个空对象(即 Object 实例),所以 A.prototype.proto 指向构造函数(Object)的 prototype 属性。

五、原生构造函数的继承

原生构造函数是指语言内置的构造函数,通常用来生成数据结构。ECMAScript 的原生构造函数有:Boolean(),Number(),String(),Array(),Date(),Function(),RegExp(),Error(),Object()。
以前,这些原生构造函数是无法继承的,比如不能自己定义一个 Array 的子类。

上例定义了一个继承 Array 的 MyArray 类。但是这个类的行为与 Array 完全不一致。之所以发生这种情况,是因为子类无法获得原生构造函数的内部属性,通过 Array.apply() 或者分配给原型对象都不行。原生构造函数会忽略 apply 方法传入的 this,也就是说,原生构造函数函数的 this 无法绑定,导致拿不到内部属性。

ES5 是先建子类的实例对象 this,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数。如,Array 构造函数有一个内部属性 [DefineOwnProperty],用来定义新属性时,更新 length 属性,这个内部属性无法在子类获取,导致子类的 length 属性行为不正常。

ES6 允许继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象 this,然后再用子类的构造函数修饰 this,使得父类的所有行为都可以继承。

上例定义了一个 myArray 类,继承了 Array 构造函数,因此可以从 myArray 生成数组的实例。这意味着 ES6 可以自定义原生数据结构的子类,这是 ES5 无法做到的。同时也说明, extends 关键字不仅可以用来继承类,还可以用来继承原生构造函数。

注意:
继承 Object 的子类,有一个差异行为。

上例中,newObj 继承了 Object,但无法通过 super 方法向父类 Object 传参。这是因为 ES6 改变了 Object 构造函数的行为,一旦发现 Object 方法不是通过 new Object() 这种形式调用,ES6 规定 Object 构造函数会忽略参数。

评论 ( 0 )
最新评论
暂无评论

赶紧努力消灭 0 回复