程序员让前端飞我爱编程

【ES6】class的基本使用与继承

2018-12-09  本文已影响5人  虹猫1992

生成实例对象的传统方法是通过构造函数

// 先定义一个函数,强行叫它构造函数
function Point(x, y) {
  this.x = x;
  this.y = y;
}

// 构造函数的方法都定义在构造函数的原型上
Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

// 使用new的方式得到一个实例对象
var p = new Point(1, 2);

定义类

ES6 引入了class(类),让javascript的面向对象编程变得更加容易清晰和容易理解。类只是基于原型的面向对象模式的语法糖。

类实际上是个“特殊的函数”,就像你能够定义的函数表达式和函数声明一样,类语法有两个组成部分:类声明类表达式

  1. 类声明

类声明是定义类的一种方式,使用关键字class,后面跟上类名,然后就是一对大括号。把类需要定义的方法放在大括号中。

//定义类
class Point {
  constructor(x, y) {  // 定义构造方法
    // this关键字代表实例对象
    this.x = x;
    this.y = y;
  }

  // 定义在类中的方法不需要添加 function
  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}


//定义类,可以省略 constructor
class P {
  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}
  1. 类表达式

类表达式是定义类的另一种形式,类似于函数表达式,把一个函数作为值赋给变量。可以把定义的类赋值给一个变量,这时候变量就为类名。class关键字之后的类名可有可无,如果存在,则只能在类内部使用。

const People = class StdInfo {
    constructor(){
        console.log(StdInfo);  //可以打印出值,是一个函数
    }
}
new People();
new StdInfo();  //报错,StdInfo is not defined;

const People = class {
    constructor(){

    }
}

new People();
let person = new class {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }
}('张三');

person.sayName(); // "张三"

不存在变量提升

定义类不存在变量提升,只能先定义类后使用,跟函数声明有区别的(函数声明会被提升)。

//-----函数声明-------
//定义前可以先使用,因为函数声明提升的缘故,调用合法。
func();
function func(){}

//-----定义类-------
new Foo(); // ReferenceError
class Foo {}

类与构造函数

  1. ES6的类,完全可以看作构造函数的另一种写法。类的数据类型就是函数,类本身就指向构造函数。
class Point {
  // ...
}

typeof Point // "function"
Point === Point.prototype.constructor // true
  1. 构造函数的prototype属性,在ES6的“类”上面继续存在;类的所有方法都定义在类的prototype属性上面,Object.assign方法可以一次向类添加多个方法
class Point {
  constructor(){
    // ...
  }
}

Object.assign(Point.prototype, {
  toString(){},
  toValue(){}
});

constructor 方法

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

// 等同于
class Point {
  constructor() {}
}
  1. 一个类中只能有一个constructor函数,定义多个会报错。
  2. constructor默认返回该对象实例(即this),也可以指定返回另外一个对象。
class Foo {
  constructor() {
    return Object.create(null);
  }
}

new Foo() instanceof Foo
// false
  1. 在一个构造方法中可以使用super关键字来调用一个父类的构造方法。
class A {}

class B extends A {
  constructor() {
    super();
  }
}

类的实例对象

使用new命令生成类的实例对象。类必须使用new调用,否则会报错。

class Point {
  // ...
}

// 报错
var point = Point(2, 3);

// 正确
var point = new Point(2, 3);

类的所有实例共享一个原型对象。

var p1 = new Point(2,3);
var p2 = new Point(3,2);

p1.__proto__ === p2.__proto__   //true

Class 的静态方法

  1. 类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Foo {
  static classMethod() {
    return 'hello';
  }
}

Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
  1. 如果静态方法包含this关键字,这个this指的是类,而不是实例。
class Foo {
  static bar () {
    this.baz();
  }
  // 静态方法可以与非静态方法重名
  static baz () {
    console.log('hello');
  }
 
  baz () {
    console.log('world');
  }
}

Foo.bar() // hello
  1. 父类的静态方法,可以被子类继承。
class Foo {
  static classMethod() {
    return 'hello';
  }
}

class Bar extends Foo {
}

Bar.classMethod() // 'hello'
  1. 静态方法也是可以从super对象上调用的。
class Foo {
  static classMethod() {
    return 'hello';
  }
}

class Bar extends Foo {
  static classMethod() {
    return super.classMethod() + ', too';
  }
}

Bar.classMethod() // "hello, too"

new.target 属性

  1. new是从构造函数生成实例对象的命令。在构造函数之中,new.target属性返回new命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。
function Person(name) {
  if (new.target !== undefined) {
    this.name = name;
  } else {
    throw new Error('必须使用 new 命令生成实例');
  }
}

// 另一种写法
function Person(name) {
  if (new.target === Person) {
    this.name = name;
  } else {
    throw new Error('必须使用 new 命令生成实例');
  }
}

var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三');  // 报错
  1. Class 内部调用new.target,返回当前 Class
class Rectangle {
  constructor(length, width) {
    console.log(new.target === Rectangle);
    this.length = length;
    this.width = width;
  }
}

var obj = new Rectangle(3, 4); // 输出 true
  1. 子类继承父类时,new.target会返回子类。
class Rectangle {
  constructor(length, width) {
    console.log(new.target === Rectangle);
    // ...
  }
}

class Square extends Rectangle {
  constructor(length) {
    super(length, length);
  }
}

var obj = new Square(3); // 输出 false

class的继承

  1. Class 可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。使用继承的方式,子类就拥有了父类的方法。
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); // 调用父类的constructor(x, y)
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString(); // 调用父类的toString()
  }
}
  1. 如果子类中有constructor构造函数,则必须使用调用super。如果不调用super方法,子类就得不到this对象。
class Point { /* ... */ }

class ColorPoint extends Point {
  constructor() {
  }
}

let cp = new ColorPoint(); // ReferenceError
  1. 在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    this.color = color; // ReferenceError
    super(x, y);
    this.color = color; // 正确
  }
}

super 关键字

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

  1. super作为函数调用时,代表父类的构造函数。ES6要求,子类的构造函数必须执行一次super函数。
class A {
  constructor() {
    console.log(new.target.name);
  }
}
class B extends A {
  constructor() {
    super();
  }
}
new A() // A
new B() // B

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

  1. super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A {
  p() {
    return 2;
  }
}

class B extends A {
  constructor() {
    super();
    console.log(super.p()); // 2
  }
}

let b = new B();

上面代码中,子类B当中的super.p(),就是将super当作一个对象使用。这时,super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()

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

参考文章

上一篇下一篇

猜你喜欢

热点阅读