ES6 Class

136 阅读5分钟

1.ES6之前的JS面向对象编程中,如果定义一个构造函数,一般来说是这样:

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.say = function() {
  return 'My name is ' + this.name + ', I am ' + this.age + ' years old';
}

这种写法跟传统的面向对象语言(比如C++Java)差异很大,对于一些新学习JS的程序员来说不太容易理解,ES6引入了Class这个概念,提供了更接近传统语言的写法,上述代码用ES6实现就是:

class Person {
  constructor(name, age ) {
    this.name = name;
    this.age = age;
  }

  say() {
    return 'My name is ' + this.name + ', I am ' + this.age + ' years old';
  }
}

上述代码中的constructor方法,就是构造方法,而this关键字则指向实例对象

2.constructor方法是类的默认方法,通过new命令生成实例对象时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,会被默认添加一个空的constructor方法:

class Person {
}

// 等同于
class Person {
  constructor() {}
}

3.constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象,但实际开发中不建议这么做:

class Foo {
  constructor() {
    return Object.create(null);
  }
}

new Foo() instanceof Foo  // false

此时constructor函数返回一个全新的对象,结果导致实例对象不是Foo类的实例,因此会返回 false

4.使用new运算符用于生成类的实例对象,如果忘记加上new,像函数那样调用Class,将会报错:

class Person {
  // ...
}

// 报错
var person = Person();

// 正确
var person = new Person();

5.实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在Class的原型对象上,并且,类的所有实例共享一个原型对象

class Person {
  // 实例属性
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  // 原型对象的属性
  say() {
    return 'My name is ' + this.name + ', I am ' + this.age + ' years old';
  }
}

var person = new Person('asong', 25);
person.hasOwnProperty('name')                   // true
person.hasOwnProperty('age')                    // true
person.hasOwnProperty('say')                    // false
person.__proto__.hasOwnProperty('say')          // true

上述代码中,nameage为实例对象person自身的属性(因为定义在this变量上) ,所以hasOwnProperty方法返回true,而say是原型对象的属性(因为定义在Person类上),所以hasOwnProperty方法返回false

6.可以在一个类的方法前,加上static关键字,声明其为“静态方法”,这样就表示该方法不会被实例继承,而是直接通过类来调用

class Foo {
  static classMethod() {
    return 'Hello World';
  }
}

Foo.classMethod()   // 'Hello World'

var foo = new Foo();
foo.classMethod()   // TypeError: foo.classMethod is not a function

7.父类的静态方法,可以被子类继承,也可以从super对象上调用

class Parent {
  static classMethod() {
    return 'Hello World';
  }
}

// 子类继承
class Child extends Parent {
}

Child.classMethod() // 'Hello World'

// super对象上调用
class Child extends Parent {
  static classMethod() {
    return super.classMethod();
  }
}

Child.classMethod()  // 'Hello World'

8.静态属性指的是Class本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性,并且目前只能有以下方式定义

class Foo {
}

Foo.prop = 1;
Foo.prop // 1

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

class Parent {
  // ...
}

// 子类继承
class Child extends Parent {
}

上面代码定义了一个Child类,该类通过extends关键字,继承了Parent类的所有属性和方法

10.子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象

class Parent {
  constructor(name, age ) {
    this.name = name;
    this.age = age;
  }
}

// 子类继承
class Child extends Parent {
  constructor(name, age, hobby) {
    super(name, age);  // 调用父类的constructor(name, age)
    this.hobby = hobby;
  }

  say() {
    return 'My name is ' + this.name + ', I am ' + this.age + ' years old , I like ' + this.hobby;
  }
}

11.super关键字,既可以当作函数使用,也可以当作对象使用

  • 作为函数调用时,代表父类的构造函数,并且只能用在子类的构造函数之中,用在其他地方就会报错
  • 作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类

需要注意的是,super指向的是父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的

class A {
  constructor() {
    this.name = 'asong';
  }
}

class B extends A {
  getName() {
    return super.name;
  }
}

let b = new B();
b.getName()                // undefined

 12.ES6规定,通过super调用父类的方法时,super会绑定子类的this

class A {
  constructor() {
    this.name = 'asong';
  }
  sayName() {
    console.log(this.name);
  }
}

class B extends A {
  constructor() {
    super();
    this.name = 'xiaoyu';
  }
  getName() {
    super.sayName();
  }
}

let b = new B();
b.getName() // 'xiaoyu'

上面代码中,super.sayName()虽然调用的是A.prototype.sayName(),但是A.prototype.sayName()会绑定子类Bthis,导致输出的是'xiaoyu',而不是'asong'。也就是说,实际上执行的是super.sayName.call(this)

13.由于绑定子类的this,因此如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性

class A {
  constructor() {
    this.name = 'asong';
  }
}

class B extends A {
  constructor() {
    super();
    this.name = 'xiaoyu';
    super.name = 'lanlan';
  }

  sayName() {
     console.log(super.name);  // undefined
     console.log(this.name);   // lanlan
  }
}

let b = new B();
b.sayName();

上面代码中,super.name赋值为'lanlan',这时等同于对this.name赋值为'lanlan'。而当读取super.name的时候,读的是A.prototype.name,而A的原型对象上没有name属性,所以返回undefined

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

class Parent {
  static myMethod() {
    console.log('static');
  }

  myMethod() {
    console.log('instance');
  }
}

class Child extends Parent {
  static myMethod() {
    super.myMethod();
  }

  myMethod() {
    super.myMethod();
  }
}

Child.myMethod(); // static 

var child = new Child();
child.myMethod(); // instance 

上述代码表明super在静态方法之中指向父类,在普通方法之中指向父类的原型对象

15.Class作为构造函数的语法糖,同时有prototype属性和 _proto_属性,因此同时存在两条继承链

class A {
}

class B extends A {
}

B._proto_ === A                         // true
B.prototype._proto_ === A.prototype     // true

16.extends关键字不仅可以用来继承类,还可以用来继承原生的构造函数。因此可以在原生数据结构的基础上,定义自己的数据结构

class myArray extends Array {
  constructor() {
    super();
    this.name = 'asong';
  }
}