ES6新特性 class(类定义和使用)

intro

在ES6中,引入了class关键字用于快速地定义“类”。
在JS中,“类”的本质是function
可以将其看做一个语法糖,让对象原型的写法更简洁清晰,更像面向对象编程的语法。

涉及关键字:

class   声明类模板
constructor 构造方法
get     getter方法
set     setter方法
static  静态成员
extends 继承
super   在子类中表示父类对象

类定义

  • 常规
    类名不能重复定义。
    class的本质是函数function
class Person {
  constructor(a) {
    this.a = a;
  }
}

console.log(typeof Person);  // function

// class Person {}  // 报错:不能重复定义
// Uncaught SyntaxError: Identifier 'Person' has already been declared
  • 类表达式
    将类定义部分作为一个表达式,赋值(其地址的值)给一个变量。
    类表达式中的类名 可省略可重复
    创建对象时,使用的是接受类表达式(地址)的变量名 充当类名。
    如果使用类表达式中的类名创建对象,会报错。
var p = class Person {};  // 有名类表达式
new p();         // Person {}
// new Person(); // Uncaught ReferenceError: Person is not defined

var p = class {}; // 匿名类表达式

var a = class AAA {};
var b = class AAA {}; // 不报错
console.log(a == b);  // false, a和b是两个不同的类(类声明)。
  • 类定义不会提升(hoisting)
    使用class简洁地定义类,应该先定义,后使用。否则报错。

  • 类中的方法

    • 方法不能使用function关键字。

    • 方法之间使用分隔符;,可省略(会自动补全)。

    • 原型变量之间的分隔符也是;

静态成员和原型成员

ES6中,虽然新增了class关键字用于类定义,但其实质还是function,只不过是为了方便书写,随后经过了某种源代码编译过程。

prototype仍然存在。在类体中定义的方法还是定义在prototype上。

class Person {}
类 :Person
原型:Person.prototype

  • 原型成员
    static关键字的成员。
    this.xxx
    类名.prototype.xxx

    class Person {
      // xxx 
      name = "无";
      eat() {   // 原型方法 推荐写法
        console.log("eat func");
      };
      // this.xxx 打印对象时会打印出来。
      constructor(a) {
        this.age = a;   // 原型属性 推荐写法
        this.drink = function() {
          console.log("drink func");
        };
      };
    }
    // 类名.prototype.xxx 打印对象时不会打印。但可以访问到。
    Person.prototype.gender = "男";      // 不推荐
    Person.prototype.foo = function() {  // 可以
      console.log("foo func");
    };
    
    var a = new Person(22);
    a;                 // Person {name: "无", age: 22, drink: ƒ}
    Person.prototype; // {gender: "男", foo: ƒ, constructor: ƒ, eat: ƒ}
    

    推荐:

    • 原型属性就在constructor中用this.xxx即可。
    • 原型方法推荐写在类体中,也可以手动添加到类名.prototype对象中。这两种都行。
    • 统一规则,尽量避免出现其他写法。
  • 静态成员
    带关键字static
    类名.xxx

    ES6中规定,class内部只有静态方法,没有静态属性。
    但是在新提案中,有静态属性。
    现阶段:

    • 构造方法constructor()中不能调用静态成员(方法,属性)
    • 静态方法中不能调用静态属性(不论类体内,还是外部)。
    • 否则报错: xxx is not defined
    class Person {
      stativ version = "1.0";   // 不推荐,并非ES6中实现的标准
      static sum(a, b) {return a + b;}; // 静态方法 推荐写法
      constructor() {}
    }
    Person.desc = "人类";   // 静态属性 推荐写法
    Person.sub = function(a, b) {return a - b;};
    

    因为版本实现问题,推荐写法:

    • 静态属性,一定在类体外用类名.xxx = value;定义。
    • 静态方法写在类体内部用static声明,或写在外部也可以(两种都行)。
      NOTE
    • 构造方法中不得访问静态成员。
    • 静态成员之间不得互相调用(静态方法内访问静态属性,静态方法互相调用)
  • 向对象添加成员(成员方法,成员属性)
    类名添加成员,就相当于静态成员。
    类名.prototype添加成员,就相当于原型成员。

有两种方法:
obj.key = value;
Object.assign(target, ...sources);

常见方法

  • constructor
    constructor方法是类的默认构造方法,创建类对象的时候被调用。
    可以在constructor方法中返回指定的类型对象(可与本类不相同)。
class One {
  constructor() {
    console.log("One constructor()");
    // return this; // 默认返回this实例对象
  }
}

class Two {
  constructor() {
    return new One();
  }
}

var a = new Two();  // One constructor()
console.log(a instanceof Two);  // false
console.log(a instanceof One);  // true

可见,使用new操作符实例化出的对象,实质是什么类型,取决于其构造方法constructor()的返回值。

  • getters, setters
    • 关键字setget,置于方法名前(如get name() {...}set name() {...})。
    • setter, getter中访问成员属性的格式:this._xxx而非this.xxx
    • 任何读操作都会调用get方法
    • 任何写操作都会调用set方法
      • 构造方法内部this.prop = value;
      • 类体外部obj.prop = value;
    • getter, setter不可单独出现
      • 只有setter,不能通过obj.xxx读取xxx的值。但可以通过打印obj查看xxx的值。
      • 只有getter
        • 如果constructor()中有对应的this.xxx=value;的语句,则 因为没有setter,所以实例化时会报错。
        • 如果constructor()中没有this.xxx=value;的语句。则实例化时不报错。
          但是,没有意义(这个属性不set值,用不了,只能getundefined的值)。
    • getter, setter必须同级出现(继承关系中)。
class Person {
  constructor(age) {
    this.age = age;
  };
  get age() {
    console.log("get age()");
    return this._age; // 注意是this._age
  };
  set age(age) {
    console.log("set age()");
    this._age = age;  // 注意是this._age
  }
}

var p = new Person(22); // set age()
console.log(p);         // Person {_age: 22}
console.log(p.age);     // 22
console.log(p._age);    // 22

getter, setter中调用原型属性:this._xxx
而非this.xxx,否则会在实例化对象(调用构造方法时)报错RangeError

  • decorator
    decorator是一个函数,用来修改类的行为,在代码编译时产生作用。
    decorator方法不是ES6标准,是ES7的新提案之一。
    后期再深入。

extends

关键字:extends, super

  • super

    • super表示父类。
    • super(...argsList)表示父类的构造方法
    • super.xxx可以访问父类的成员属性
    • super.xxx()可以访问父类的成员方法
  • 在子类的构造犯法中访问this或返回返回值之前,必须先调用父类的构造方法super()
    Must call super constructor in derived class before accessing 'this' or returning from derived constructor

class Father {
  constructor(firstName) {
    console.log("father...");
    this.firstName = firstName;
  }
}
class Child extends Father {
  constructor(firstName, lastName) {
    super(firstName);
    console.log("child...");
    this.lastName = lastName;
  }
}

Father.prototype.isPrototypeOf(Child.prototype);  // true

var a = new Child("J", "DD");
// father...
// child...
// Child {firstName: "J", lastName: "DD"}
  • 子类中调用父类的方法
    • 原型方法:在子类的原型方法中super.xxx(),其中xxx为原型方法名。
    • 静态方法:在子类的静态方法中super.xxx()父类名.xxx(),其中xxx为静态方法名。
class Father {
  foo() {console.log("foo...");}
  static bar() {console.log("bar...")}
}

class Child extends Father {
  f() {
    super.foo();  // 在子类的原型方法中调用父类的原型方法
  }
  static b() {
    super.bar();  // 在子类的静态方法中调用父类的原型方法
    Father.bar();
  }
}

Child.b()   // 用类名调用静态方法
// bar...
// bar...
(new Child()).f() // 用对象调用原型方法
// foo...

即:
父类的构造方法可以在子类的构造方法中被调用。
父类的原型方法可以在子类的原型方法中被调用。
父类的静态方法可以再子类的静态方法中被调用。
否则出错。

  • 不可以继承对象字面量
    var obj = {name:"JT", showName:function() {console.log(this.name)}};
    如果想定义一个类Child,也拥有这个对象的成员。
  1. 直接继承对象字面量 - 不可以,报错。
    Uncaught TypeError: Class extends value # is not a constructor or null
    var Father = {name:"JT"};
    class Child extends Father {};
    
    1. 操作原型链
    var Father = {name:"JT"};
    class Child {};
    Object.setPrototypeOf(Child.prototype, Father);
    
    (new Child()).name;   // "JT"
    

    你可能感兴趣的:(JS)