我们除了可以使用 new function 的方式,使用构造函数创建实例对象外,还可以使用新引入的“class”语法创建实例对象,这个新引入的语法特性对面向对象编程更加友好和有用。

“class”语法

基本语法如下:

  1. class MyClass {
  2. // 类的方法
  3. constructor() { ... }
  4. method1() { ... }
  5. method2() { ... }
  6. method3() { ... }
  7. ...
  8. }

这里声明了一个名为 MyClass 的类,然后我们就可以用 new MyClass() 方式创建对象了。新对象中就包含了上述列举的几个方法。

举个例子:

  1. class User {
  2. constructor(name) {
  3. this.name = name;
  4. }
  5. sayHi() {
  6. alert(this.name);
  7. }
  8. }

new MyClass("John") 的调用结果如下:

  1. 会创建一个新对象。
  2. constructor 携带给定参数 "John" 执行,并将值赋给了 this.name

然后,我们就可以调用诸如 user.sayHi() 的对象方法了。

⚠️ 类方法之间无需使用逗号 ** 开发者在这里常犯的错误就是在类方法之间用逗号(,)隔开。这不是不对的,会引发语法错误。

注意,不要把类语法跟对象字面量语法搞混淆了。在类中,方法之间是无需使用逗号。

什么是类?

其实 class 并不想某些人认为的那样,是 JavaScript 中引入的新的语言层面的实体。

下面我们将来揭开类的神秘面纱,帮助你理解这背后的运行机制。

JavaScript 中,类就是个函数。

不信,来看一下:

  1. class User {
  2. constructor(name) { this.name = name; }
  3. sayHi() { alert(ths.name); }
  4. }
  5. // 证据来了
  6. alert(typeof User); // function。User 是个函数

class User {...} 做了下面这些事情:

  1. 创建一个名为 User 的函数,也就是类声明后的返回结果。函数体代码使用的是 constructor 方法里的(如果我们没写的话,那就是空的)。
  2. 将诸如 sayHi 的类方法存储在 User.prototype 上。

new User 创建完对象后,在对象上调用的方法,就是从原型上取的。因此,对象就可以访问类中的方法了。

我们用下面这张示例图,展示了 class User 做了哪些事情:

image.png

从下面的代码,能看出类背后的运行机制:

  1. class User {
  2. constructor(name) { this.name = name; }
  3. sayHi() { alert(this.name); }
  4. }
  5. // 类是个函数
  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 呀就是个“语法糖”(是指在不引入新东西的情况下,让代码变得更加易读而设计的语法)。这是因为,我们实际上可以在不使用 class 关键字的情况下写出相同功能的代码:

  1. // 使用纯函数重写 class User
  2. // 1. 创建构造函数
  3. function User(name) {
  4. this.name = name;
  5. }
  6. // 所有函数的原型对象上都有一个默认的 constructor 属性
  7. // 因此无需我们手动创建
  8. // 2. 像原型添加方法
  9. User.prototype.sayHi = function() {
  10. alert(this.name);
  11. };
  12. // 使用:
  13. let user = new User("John");
  14. user.sayHi();

以上代码得到的结果,可以近似等于 class User {...} 的书写形式。因此,这就是为什么可以把 class 当成是定义构造函数和构造函数原型上方法的一个语法糖。

当然,也是有区别的:

  1. 首先,通过 class 创建的函数会使用一个特别的内部属性 [[FunctionKind]]:"classConstructor" 标记。因此与手动创建的方式还是不完全一样的。

与普通函数不同的是,类构造函数必须要用 new 调用,否则就报错:

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

而且,在大多数 JavaScript 引擎中,类构造器的字符串表示通常是以“class…”开头的。

  1. class User {
  2. constructor() {}
  3. }
  4. alert(User); // class User { ... }
  1. 类方法都是不可枚举的。类定义会将 "prototype" 中方法的 enumerable 标记都设置为 false

这样一来,用 for..in 去遍历对象实例的时候,不会出现这些类方法。

  1. 类代码内部默认都是启用了严格模式("use strict")。所有类内部代码都是在严格模式下执行的。

除此之外,class 语法还有其他特性。咱们接着来讨论。

类表达式

与函数类似,类可以在一个表达式中定义、传递、返回、赋值等等。

下面举了一个类表达式的列子:

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

与命名函数表达式类似,类表达式也可以有名字。

不过类表达式的名字,只能在类内部引入使用:

  1. // “命名类表达式”
  2. // (规范中无此概念,但它类似于命名函数表达式)
  3. let User = class MyClass {
  4. sayHi() {
  5. alert(MyClass); // MyClass 这个名称只能在类内部引入使用
  6. }
  7. };
  8. new User().sayHi(); // class MyClass {...}。这里能成功展示 MyClass 类的定义代码
  9. alert(MyClass); // ReferenceError: MyClass is not defined。MyClass 这个

我们甚至可以“按需”动态地创建类:

  1. function makeClass(phrase) {
  2. // 声明并返回一个类
  3. return class {
  4. sayHi() {
  5. alert(phrase);
  6. };
  7. };
  8. }
  9. // 创建一个类
  10. let User = makeClass("Hello");
  11. new User().sayHi(); // Hello

Getters/setters

类似于字面量对象,类中也能包含 getters/setters,还有计算属性这些。

下列的 user.name 就是靠 get/set 实现的:

  1. class User {
  2. constructor(name) {
  3. // 调用 setter
  4. this.name = name;
  5. }
  6. get name() {
  7. return this._name;
  8. }
  9. set name(value) {
  10. if (value.length < 4) {
  11. alert("Name is too short.");
  12. return;
  13. }
  14. this._name = value;
  15. }
  16. }
  17. let user = new User("John");
  18. alert(user.name); // John
  19. user = new User(""); // Name is too short.

类声明在 User.prototype 上创建了 getters、setters。类似下面这样:

  1. Object.defineProperties(User.prototype, {
  2. name: {
  3. get() {
  4. return this._name
  5. },
  6. set(name) {
  7. // ...
  8. }
  9. }
  10. });

同时,我们还可以在类中使用方括号 […] 声明计算属性:

  1. class User {
  2. ['say' + 'Hi']() {
  3. alert("Hello");
  4. }
  5. }
  6. new User().sayHi();

类属性

⚠️ 可能需要 polyfill
类属性是最近新添加到语言中的特性。

上例中 User 只有方法。现在来为它添加属性:

  1. class User {
  2. name = "Anonymous";
  3. sayHi() {
  4. alert(`Hello, ${this.name}!`);
  5. }
  6. }
  7. const user = new User()
  8. user.sayHi(); // Hello, Anonymous!
  9. user.name; // Anonymous
  10. alert(User.prototype.sayHi); // 存在于 User.prototype 上面
  11. alert(User.prototype.name); // undefined。没在 User.prototype 上面

总结

类的基本语法如下:

  1. class MyClass {
  2. prop = value; // 属性
  3. constructor(...) { // 构造器
  4. // ...
  5. }
  6. method(...) {} // 方法
  7. get something(...) {} // getter 方法
  8. set something(...) {} // setter 方法
  9. [Symbol.iterator]() {} // 使用计算属性声明的方法(Symbol 类型)
  10. // ...
  11. }

MyClass 从技术上讲是个函数(函数体就是在 constructor 中定义的代码)。而方法、getters、setters 则是定义在 MyClass.prototype 上的。

(完)


📄 文档信息

🕘 更新时间:2020/01/20
🔗 原文链接:https://javascript.info/class