ES6 —(Class 的继承)

1、简介

  Class 可以通过 extends 关键字实现继承。

class Point{
    constructor(x, y){
        this.x = x;
        this.y = y;
    }
    toString(){
        return `( ${this.x}, ${this.y})`
    }
}

class ColorPoint extends Point{
    constructor(x, y, color){
        super(x, y);
        this.color = color;
    }
    toString(){
        return `${this.color} ${super.toString()}`;
    }
}

var cp = new ColorPoint(1, 2, 'red');
console.log(cp.toString());  => red ( 1, 2)

注意
  (1)如果在子类中显式定义了 constructor 则子类必须在 constructor 方法中调用 super 方法,否则会报错。因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后进行加工,如果不调用 super 方法,子类就得不到 this 对象。并且 this 的使用是在 super 方法调用之后。否则会报错。

class Poi{}
class CPoi{
    constructor(){}
}  
var cpo = new CPoi(); => 报错  this is not defined
--------------------------------------------------
class Poi{}
class CPoi{
    constructor(c){
        this.c = c;
        super();
    }
}
var cpo = new CPoi(); => 报错  this is not defined

  (2)子类的实例对象既是 子类的实例 又是 父类的实例。

cp instanceof ColorPoint => true;
cp instanceof Point => true;

  (3)Object.getPrototypeOf() 方法可以用来从子类上获取父类。可以使用该方法判断,一个类是否继承了另一个类。

Object.getPrototypeOf(ColorPoint) === Point => true

2、super 关键字

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

(1)super 作为函数调用
  super 作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次 super()
注意:
  1)super 虽然代表了父类的构造函数,但是返回的是子类的实例,即 super 内的 this 指的是子类,因此 super() 相当于 Point.prototype.constructor.call(this)。参见new.target属性
  2)作为函数时,super() 只能用在子类的构造函数中,用在其他地方将报错。

(2)super 作为对象
  super 作为对象时,在普通方法中,指向父类的原型对象,在静态方法中,指向父类。
  例如 ColorPointtoString 方法中的 super.toString() 就是将 super 当作一个对象使用,这时,super 在普通方法中,指向 Point.prototype ,所以 super.toString() 相当于 Point.prototype.toString()
注意:
  1)由于 super 在普通方法中指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过 super 调用的。

class A { constructor(){this.x = 2;}} 
class B extends A { getX() { return super.x;} }
let b = new B();
b.getX() => undefined

如果属性定义在父类的原型对象上,super 就可以取到。

class A{}
A.prototypy.x = 2;// 此时 B 中可通过 super.x 的到 x 

  2)ES6 规定,通过 super 调用父类的方法时, super 会绑定子类的 this。
  3)由于绑定子类的 this ,所以如果通过 super 对某个属性赋值,这时 super 就是 this,赋值的属性会变成子类实例的属性。

class A {
    constructor(){
        this.x = 2;
    }
}
class B extends A {     
    constructor() { 
        super();
        this.x = 2;
        super.x = 3;
        console.log(super.x); // undefined
        console.log(this.x); // 3
    }
}
let b = new B();

上述代码中,赋值时 super 就是 this ,即 super.x = 3 => this.x = 3 ,而当读取时 super 代表父类的原型对象,即 super.x => A.prototype.x 返回 undefined
  4)使用 super 时,必须显式指定是作为函数,还是作为对象,否则会报错。

  如果 super 作为对象,用在静态方法中,这时 super 将指向父类,而不是父类的原型对象。

3、类的 prototype 属性和 __proto__ 属性

  每个对象都有 __proto__ 属性,指向对应的构造函数的 prototype 属性。Class 作为构造函数的语法糖,同时有 prototype 属性和 __proto__ 属性,因此同时存在两条继承链。
(i)子类的 __proto__属性,表示构造函数的继承,总是指向父类。
(ii)子类 prototype 属性的 __proto__属性,表示方法的继承,总是指向父类的 prototype 属性。

B.__proto__ === A  // true
B.prototype.__proto__ = A.prototype  // true

  子类实例的 __proto__ 属性的 __proto__ 属性,指向父类实例的 __proto__ 属性。也就是说,子类的原型的原型,是父类的原型。

b.__proto__.__proto__ === a.__proto__ // true

(1)extends 的继承目标
  extends 关键字后面可以跟多种类型的值。
  (1)函数
  由于函数都有 prototype 属性(除了 Function.prototype 函数),因此,可以跟任何函数。
  (2)Object
  子类继承 Object 类,这种情况下,子类其实就是构造函数 Object 的复制, 子类的实例就是 Object 的实例。

class A extends Object {}
A.__proto__ === Object // true
A.prototype.__proto__ === Object.prototype // true 

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

class A {}
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === Object.prototype // true

  (4)子类继承 null
  这种情况与第三种情况非常像。 A 也是一个普通函数,所以直接继承 Function.prototype。但是,A 调用后返回的一个 undefined 。

class A extends null {}
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === undefined // true
=> 相当于
class A extends null {
    constructor(){ return Object.create(null);}
}

4、原生构造函数的继承

  原生构造函数是指语言内置的构造函数,通常用来生成数据结构。 ECMAScript 的原生构造函数大致有以下:

  • Boolean()
  • Number()
  • String()
  • Array()
  • Date()
  • Function()
  • RegExp()
  • Error()
  • Object()

  以前,这些原生构造函数无法继承,参见 ES5 的继承。ES6 允许使用 extends 继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象 this ,然后在用子类的构造函数修饰 this ,使得父类的所有行为都可以继承。
注意:继承 Object 的子类,有一个行为差异。

class NewObj extends Object{
    constructor(){
        super( ...arguments);
    }
}
var o = new NewObject({attr: true});
o.attr === true // false

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

5、Mixin 模式

  Mixin 模式是指,将多个类的接口 “混入”(mix in)另一个类。 它在 ES6 的实现如下:

function mix(...mixins){
    class Mix{}
    for(let mixin of mixins){
        copyPrototypes(Mix, mixin);
        copyPrototypes(Mix.prototype, mixin.prototype);
    }
    return Mix;
}
function copyPrototypes(target, source){
    for(let key of Reflect.ownKeys(source)){
        if( key !== "constructor"
         && key !== "prototype"
         && key !== "name" ){
            let desc = Object.getOwnPrototyDescriptor(source, key);
            Object.defineProperty(target, key, desc);
        }
    }
}

上面的 mix 函数,可以将多个对象合成一个类,使用的时候只要继承这个类即可。

class DistributedEdit extends mix(Loggable, Serializable) {}

阮一峰:ECMAScript 6入门

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值