在 JavaScript 的面向对象编程中,我们通常关注实例 (instance) 的属性和行为。然而,在某些场景下,我们需要定义一些不属于任何特定实例,而是属于类本身 (class itself) 的方法和数据。这时,static 关键字就派上了用场。

ES6 (ECMAScript 2015) 引入的 class 语法糖,以及后续版本(如 ES2022)中对静态属性 (static properties) 的支持,使得在 JavaScript 中定义类层面的成员变得直观而强大。static 成员(包括方法和属性)不通过类的实例调用,而是直接通过类名调用。它们常用于实现工具函数、工厂方法、常量定义或与特定实例状态无关的操作。

本教程将深入探讨 JavaScript static 关键字的各个方面,包括静态方法和静态属性的定义、它们与实例成员的区别、this 关键字在静态方法中的行为、继承机制以及实际应用场景。通过本教程,您将能够彻底掌握 static 成员,并将其应用于构建更具结构化和可维护性的现代 JavaScript 应用程序。


前置知识

在开始本教程之前,建议您具备以下 JavaScript 基础知识:

  • 变量与数据类型: 了解 JavaScript 的基本数据类型和对象类型。
  • 函数: 熟悉函数的声明、调用、参数以及 return 语句。
  • 对象字面量: 了解 const obj = { key: 'value' }; 这种创建对象的方式。
  • class 语法: 了解类的基本声明、constructor 方法和实例方法的定义。
  • this 关键字:this 的上下文绑定规则有初步了解,特别是在类方法中的行为。

目录

  1. static 关键字简介
    • 1.1 什么是 static 成员?
    • 1.2 为什么需要 static 成员?
  2. static 方法
    • 2.1 定义静态方法
    • 2.2 调用静态方法
    • 2.3 this 在静态方法中的行为
    • 2.4 静态方法与实例方法的区别
  3. static 属性 (ES2022+)
    • 3.1 定义静态属性
    • 3.2 访问静态属性
    • 3.3 静态属性与实例属性的区别
    • 3.4 传统实现静态属性的方式 (了解)
  4. static 成员的继承
    • 4.1 继承机制
    • 4.2 派生类中的 super 关键字
  5. static 成员的常见应用场景
    • 5.1 工具函数集合
    • 5.2 工厂方法
    • 5.3 常量或配置
    • 5.4 单例模式
  6. 总结
  7. 附录:术语表

1. static 关键字简介

1.1 什么是 static 成员?

在 JavaScript class 中,使用 static 关键字定义的方法属性被称为静态成员。它们是属于类本身的,而不是属于类的任何特定实例。这意味着:

  • 它们存在于类(构造函数)上,而不是实例的 prototype 链上。
  • 它们可以直接通过类名来调用或访问,而不需要先创建类的实例。
  • 它们不能直接访问实例的属性或方法(除非显式传入实例作为参数)。
1.2 为什么需要 static 成员?

static 成员主要用于:

  • 提供与类相关但与特定实例无关的功能: 例如,一个 Math 类可能有一个 add 方法,它接受两个数字并返回它们的和,这个方法不需要任何 Math 实例就可以工作。
  • 作为工厂方法: 用于创建和返回类的实例,封装实例化的复杂逻辑。
  • 存储类级别的配置或常量: 例如,一个 Config 类可以存储一个 static 属性 DEFAULT_PORT
  • 实现单例模式: 确保一个类只有一个实例。

2. static 方法

2.1 定义静态方法

class 内部,使用 static 关键字后跟方法名来定义静态方法。

class Calculator {
  // 静态方法
  static add(a, b) {
    return a + b;
  }

  static subtract(a, b) {
    return a - b;
  }

  // 实例方法
  multiply(a, b) {
    return a * b;
  }
}
2.2 调用静态方法

静态方法直接通过类名调用,不能通过类的实例调用。

// 调用静态方法
console.log(Calculator.add(10, 5));     // 15
console.log(Calculator.subtract(10, 5));  // 5

// 尝试通过实例调用静态方法会导致错误
const calc = new Calculator();
// console.log(calc.add(10, 5)); // TypeError: calc.add is not a function

// 调用实例方法
console.log(calc.multiply(10, 5)); // 50
2.3 this 在静态方法中的行为

在静态方法内部,this 关键字指向类本身(即构造函数),而不是类的实例。这意味着你可以通过 this 访问其他静态成员。

class Logger {
  static debugMode = true; // 静态属性 (ES2022+)

  static log(message) {
    if (this.debugMode) { // this 在这里指向 Logger 类本身
      console.log(`[DEBUG] ${message}`);
    } else {
      console.log(`[INFO] ${message}`);
    }
  }

  static enableDebug() {
    this.debugMode = true; // 修改静态属性
  }

  static disableDebug() {
    this.debugMode = false;
  }
}

Logger.log("Application started."); // [DEBUG] Application started.

Logger.disableDebug();
Logger.log("Debug mode disabled."); // [INFO] Debug mode disabled.

Logger.enableDebug();
Logger.log("Debug mode re-enabled."); // [DEBUG] Debug mode re-enabled.
2.4 静态方法与实例方法的区别
特性 静态方法 实例方法
所属对象 属于类本身 属于类的每个实例
调用方式 通过类名直接调用 (ClassName.method()) 通过实例调用 (instance.method())
this 指向类本身 (构造函数) 指向调用该方法的实例
访问实例 不能直接访问实例属性/方法 可以访问实例属性/方法 (this.prop)
原型链 不在实例的原型链上 存在于实例的原型链上 (ClassName.prototype)
内存 所有实例共享一个方法引用 所有实例共享一个方法引用 (通过原型链)

3. static 属性 (ES2022+)

ES2022 (ECMAScript 2022) 引入了静态属性的官方语法,使得定义类层面的属性变得更加简洁和直观。

3.1 定义静态属性

class 内部,使用 static 关键字后跟属性名和赋值来定义静态属性。

class AppConfig {
  static VERSION = '1.0.0';
  static DEFAULT_THEME = 'dark';
  static MAX_CONNECTIONS = 100;

  static getInfo() {
    return `App Version: ${this.VERSION}, Theme: ${this.DEFAULT_THEME}`;
  }
}
3.2 访问静态属性

静态属性直接通过类名来访问。

console.log(AppConfig.VERSION);         // 1.0.0
console.log(AppConfig.DEFAULT_THEME);   // dark
console.log(AppConfig.getInfo());       // App Version: 1.0.0, Theme: dark

// 尝试通过实例访问静态属性会导致 undefined
const config = new AppConfig();
console.log(config.VERSION); // undefined
3.3 静态属性与实例属性的区别
特性 静态属性 实例属性
所属对象 属于类本身 属于类的每个实例
定义位置 类体内部,使用 static 关键字 constructor 内部或类字段 (this.propprop = value;)
访问方式 通过类名 (ClassName.property) 通过实例 (instance.property)
内存 仅一份副本,所有实例共享此值 每个实例都有自己的副本
修改 修改会影响所有通过类名访问的引用 修改仅影响当前实例
3.4 传统实现静态属性的方式 (了解)

在 ES2022 之前,通常在类定义外部直接为构造函数添加属性来实现静态属性。

class LegacyAppConfig {
  // ... 其他方法
}

LegacyAppConfig.VERSION = '0.9.0';
LegacyAppConfig.DEFAULT_LANG = 'en';

console.log(LegacyAppConfig.VERSION); // 0.9.0

这种方式功能相同,但新语法更清晰地将静态成员定义集中在类体内部。


4. static 成员的继承

静态方法和静态属性可以像实例方法一样被继承。派生类会继承其父类的所有静态成员。

4.1 继承机制

当一个类 extends 另一个类时,它不仅继承了原型链上的实例成员,也继承了类(构造函数)本身的静态成员。

class Vehicle {
  static maxSpeed = 200;
  static getVehicleType() {
    return "Generic Vehicle";
  }
  constructor(name) {
    this.name = name;
  }
}

class Car extends Vehicle {
  static wheels = 4;
  static getCarDetails() {
    return `${super.getVehicleType()} with ${this.wheels} wheels. Max Speed: ${super.maxSpeed} km/h`;
  }
}

console.log(Car.maxSpeed);            // 200 (继承自 Vehicle)
console.log(Car.getVehicleType());    // Generic Vehicle (继承自 Vehicle)
console.log(Car.wheels);              // 4 (Car 自己的静态属性)
console.log(Car.getCarDetails());     // Generic Vehicle with 4 wheels. Max Speed: 200 km/h

可以看到,Car 类可以直接访问 Vehicle 类的静态属性 maxSpeed 和静态方法 getVehicleType

4.2 派生类中的 super 关键字

在派生类的静态方法中,super 关键字用于调用父类的静态方法或访问静态属性super 在这里指代父类本身,而不是父类的原型或实例。

class Parent {
  static staticMethod() {
    return "Parent static method";
  }
  static commonProp = "Parent Common Prop";
}

class Child extends Parent {
  static staticMethod() {
    return `${super.staticMethod()} and Child enhancement`; // 调用父类的静态方法
  }
  static commonProp = `${super.commonProp} and Child Common Prop`; // 访问父类的静态属性
}

console.log(Child.staticMethod()); // Parent static method and Child enhancement
console.log(Child.commonProp);   // Parent Common Prop and Child Common Prop

5. static 成员的常见应用场景

5.1 工具函数集合

将一组相关的工具函数封装在一个类中,作为静态方法提供,无需实例化即可使用。

class StringUtils {
  static capitalize(str) {
    return str.charAt(0).toUpperCase() + str.slice(1);
  }

  static reverse(str) {
    return str.split('').reverse().join('');
  }
}

console.log(StringUtils.capitalize('hello')); // Hello
console.log(StringUtils.reverse('world'));    // dlrow
5.2 工厂方法

静态方法可以作为工厂,用于创建并返回类的实例,封装实例创建的复杂逻辑。

class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  static createGuestUser() {
    return new User('Guest', 'guest@example.com');
  }

  static createAdminUser(name) {
    // 假设这里有更复杂的权限初始化逻辑
    const admin = new User(name, `${name.toLowerCase()}@admin.com`);
    admin.role = 'admin';
    return admin;
  }
}

const guest = User.createGuestUser();
console.log(guest); // User { name: 'Guest', email: 'guest@example.com' }

const admin = User.createAdminUser('SuperAdmin');
console.log(admin); // User { name: 'SuperAdmin', email: 'superadmin@admin.com', role: 'admin' }
5.3 常量或配置

将应用程序的全局常量或配置信息作为静态属性存储,方便集中管理和访问。

class Constants {
  static API_BASE_URL = 'https://api.example.com/v1';
  static RETRY_LIMIT = 3;
  static TIMEOUT_MS = 5000;
}

console.log(Constants.API_BASE_URL); // https://api.example.com/v1
5.4 单例模式

静态方法可以用来实现单例模式,确保一个类只有一个实例。

class Database {
  constructor() {
    if (Database.instance) {
      return Database.instance;
    }
    this.connection = 'Connected to DB';
    Database.instance = this;
  }

  static getInstance() {
    if (!Database.instance) {
      Database.instance = new Database();
    }
    return Database.instance;
  }

  query(sql) {
    console.log(`${this.connection}: Executing ${sql}`);
  }
}

const db1 = Database.getInstance();
const db2 = Database.getInstance();

console.log(db1 === db2); // true (它们是同一个实例)
db1.query('SELECT * FROM users');

6. 总结

static 关键字是 JavaScript 中一个非常实用的特性,它允许您定义属于类本身而非其任何实例的成员。

本教程详细阐述了:

  • 静态方法静态属性的基本定义和调用方式。
  • this 在静态方法中指向类本身,而非实例。
  • 静态成员与实例成员之间在所属对象、调用方式和可访问性上的显著区别。
  • 静态成员如何通过 extends 关键字被子类继承,以及在子类静态方法中使用 super 访问父类静态成员的机制。
  • static 成员在实际开发中的广泛应用场景,如工具函数、工厂方法、常量存储和单例模式。

掌握 static 成员将使您能够更好地组织代码,区分类级别的功能和实例级别的功能,从而提高代码的可读性、可维护性和模块化程度。


7. 附录:术语表

  • static 关键字: 用于定义属于类本身而非其实例的成员(方法或属性)。
  • 静态方法 (Static Method): 属于类本身的方法,通过类名直接调用,不能通过实例调用。
  • 静态属性 (Static Property): 属于类本身的属性,通过类名直接访问,不能通过实例访问 (ES2022+)。
  • 实例方法 (Instance Method): 属于类的实例的方法,通过实例调用。
  • 实例属性 (Instance Property): 属于类的每个实例的属性,通过实例访问。
  • class JavaScript 中用于定义类(对象蓝图)的关键字。
  • this 关键字: 在静态方法中指向类本身;在实例方法中指向调用该方法的实例。
  • extends 关键字: 用于实现类之间的继承。
  • super 关键字: 在派生类的静态方法中,用于调用父类的静态方法或访问父类的静态属性。
  • 原型链 (Prototype Chain): JavaScript 中对象通过 [[Prototype]] 链接起来的链条,实例方法位于此链上。静态方法不在此链上。
  • 工厂方法 (Factory Method): 一种设计模式,其中静态方法用于创建并返回类的实例。
  • 单例模式 (Singleton Pattern): 一种设计模式,确保一个类只有一个实例。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐