一、在面向对象的编程中,class是用于创建对象的可扩展的程序代码模版,它为对象提供了状态(成员变量)的初始值和行为(成员函数或方法)的实现。

什么是 class?

一、在 JavaScript 中,类是一种函数。
二、看看下面这段代码:

  1. class User {
  2. constructor(name) { this.name = name; }
  3. sayHi() { alert(this.name); }
  4. }
  5. // 佐证:User 是一个函数
  6. alert(typeof User); // function

1、class User {…}构造实际上做了如下的事儿:
(1)创建一个名为User的函数,该函数成为类声明的结果。该函数的代码来自于constructor方法(如果我们不编写这种方法,那么它就被假定为空)。
(2)存储类中的方法,例如User.prototype中的sayHi。
2、当new User对象被创建后,当我们调用其方法时,它会从原型中获取对应的方法。因此,对象new User可以访问类中的方法。
3、我们可以将class User声明的结果解释为:
image.png
4、下面这些代码很好地解释了它们:

  1. class User {
  2. constructor(name) { this.name = name; }
  3. sayHi() { alert(this.name); }
  4. }
  5. // class 是一个函数
  6. alert(typeof User); // function
  7. // ...或者,更确切地说,是 constructor 方法
  8. alert(User === User.prototype.constructor); // true
  9. // 方法在 User.prototype 中,例如:
  10. alert(User.prototype.sayHi); // alert(this.name);
  11. // 在原型中实际上有两个方法
  12. alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi

“class” 语法

一、基本语法是:

  1. class MyClass {
  2. prop = value; // 属性
  3. constructor() { ... } // 构造器
  4. // class 方法
  5. method1() { ... } // method
  6. method2() { ... }
  7. method3() { ... }
  8. get something() {} // getter方法
  9. set something() {} // setter方法
  10. [Symbol.iterator]() {} // 有计算名称(computed name)的方法(此处为symbol)
  11. }

1、然后使用new MyClass()来创建具有上述列出的所有方法的新对象。
二、new会自动调用constructor()方法,因此我们可以在constructor()中初始化对象。
【示例1】

  1. class User {
  2. constructor(name) {
  3. this.name = name;
  4. }
  5. sayHi() {
  6. alert(this.name);
  7. }
  8. }
  9. // 用法:
  10. let user = new User("John");
  11. user.sayHi();

1、当new User(“John”)被调用:
(1)一个新对象被创建。
(2)constructor使用给定的参数运行,并为其分配this.name。
2、然后我们就可以调用对象方法了,例如user.sayHi。
三、类的方法之间没有逗号
1、不要把这里的符号与对象字面量相混淆。在类中,不需要逗号(在类的方法之间放置逗号,这会导致语法错误)。

不仅仅是语法糖

一、人们常说class是一个语法糖(语法糖:使内容更易阅读,但不引入任何新内容的语法),因为我们实际上可以在没有class的情况下声明相同的内容:

  1. // 用纯函数重写 class User
  2. // 1. 创建构造器函数
  3. function User(name) {
  4. this.name = name;
  5. }
  6. // 函数的原型(prototype)默认具有 "constructor" 属性,
  7. // 所以,我们不需要创建它
  8. // 2. 将方法添加到原型
  9. User.prototype.sayHi = function() {
  10. alert(this.name);
  11. };
  12. // 用法:
  13. let user = new User("John");
  14. user.sayHi();

1、这个定义的结果与使用类得到的结果基本相同。因此,这确实是将class视为一种定义构造器及其原型方法的语法糖的理由。
二、尽管,它们之间存在着重大差异:
1、首先,通过class创建的函数具有特殊的内部属性标记[[FunctionKind]]:”classConstructor”。因此,它与手动创建并不完全相同。编程语言会在许多地方检查该属性。
【示例1】与普通函数不同,必须使用new来调用它:

  1. class User {
  2. constructor() {}
  3. }
  4. alert(typeof User); // function
  5. User(); // Error: Class constructor User cannot be invoked without 'new'

2、此外,大多数 JavaScript 引擎中的类构造器的字符串表示形式都以 “class…” 开头

  1. class User {
  2. constructor() {}
  3. }
  4. alert(User); // class User { ... }还有其他的不同之处,我们很快就会看到。

(1)类方法不可枚举。
① 类定义将”prototype”中的所有方法的enumerable标志设置为false。这很好,因为如果我们对一个对象调用for..in方法,我们通常不希望 class 方法出现。
(2)类总是使用use strict。 在类构造中的所有代码都将自动进入严格模式。

类表达式

一、就像函数一样,类可以在另外一个表达式中被定义,被传递,被返回,被赋值等。
【示例1】这是一个类表达式的例子:

  1. let User = class {
  2. sayHi() {
  3. alert("Hello");
  4. }
  5. };

二、类似于命名函数表达式(Named Function Expressions),类表达式可能也应该有一个名字。
三、如果类表达式有名字,那么该名字仅在类内部可见:

// “命名类表达式(Named Class Expression)”
// (规范中没有这样的术语,但是它和命名函数表达式类似)
let User = class MyClass {
  sayHi() {
    alert(MyClass); // MyClass 这个名字仅在类内部可见
  }
};

new User().sayHi(); // 正常运行,显示 MyClass 中定义的内容

alert(MyClass); // error,MyClass 在外部不可见

四、我们可以动态地“按需”创建类,就像这样:

function makeClass(phrase) {
  // 声明一个类并返回它
  return class {
    sayHi() {
      alert(phrase);
    }
  };
}

// 创建一个新的类
let User = makeClass("Hello");

new User().sayHi(); // Hello

类包含的内容

一、就像对象字面量,类可能包括 getters/setters,计算属性(computed properties)等。

Getters/setters

一、【示例1】使用get/set实现user.name:

class User {
  constructor(name) {
    // 调用 setter
    this.name = name;
  }

  get name() {
    return this._name;
  }

  set name(value) {
    if (value.length < 4) {
      alert("Name is too short.");
      return;
    }
    this._name = value;
  }
}

let user = new User("John");
alert(user.name); // John

user = new User(""); // Name is too short.

二、从技术上来讲,这样的类声明可以通过在User.prototype中创建 getters 和 setters 来实现。

计算属性名称 […]

一、使用中括号[…]的计算方法名称示例:

class User {
  ['say' + 'Hi']() {
    alert("Hello");
  }

}

new User().sayHi();

二、这种特性很容易记住,因为它们和对象字面量类似。

Class 字段

一、旧的浏览器可能需要 polyfill
1、类字段(field)是最近才添加到语言中的。
二、之前,我们的类仅具有方法。
三、“类字段”是一种允许添加任何属性的语法。
【示例1】在class User中添加一个name属性:

class User {
  name = "John";

  sayHi() {
    alert(`Hello, ${this.name}!`);
  }
}

new User().sayHi(); // Hello, John!

1、所以,我们就只需在表达式中写 “=”,就这样。
四、类字段重要的不同之处在于,它们会在每个独立对象中被设好,而不是设在User.prototype:

class User {
  name = "John";
}

let user = new User();
alert(user.name); // John
alert(User.prototype.name); // undefined

五、可以在赋值时使用更复杂的表达式和函数调用:

class User {
  name = prompt("Name, please?", "John");
}

let user = new User();
alert(user.name); // John

使用类字段制作绑定方法

见函数绑定#使用类字段制作绑定方法:https://www.yuque.com/tqpuuk/yrrefz/qycrvp