JS `static` 属性与方法:定义类级别的工具函数或常量

各位观众,早上好(或者下午好,晚上好,取决于你正在哪个时区摸鱼)。今天咱们来聊聊 JavaScript 里那些“静态”的家伙们——static 属性和方法。他们就像类里面的老干部,不属于任何具体的实例,而是属于类本身,专门负责处理一些类级别的任务。

开场白:静态是个啥?

想象一下,你开了一家包子铺。每个包子(也就是类的实例)都有自己的馅儿和皮儿。但有些东西是属于整个包子铺的,比如包子铺的名字、包子铺的地址、以及计算所有包子总销量的函数。这些不属于任何一个单独的包子,而是属于整个包子铺。在 JavaScript 的世界里,这些就是 static 属性和方法。

static 属性:类的常量与配置

static 属性通常用于定义一些与类相关的常量或者配置信息。这些信息对于类的所有实例都是通用的,而且通常不会被修改。

举个栗子,咱们来定义一个 MathUtils 类,里面放一些数学相关的常量:

class MathUtils {
  static PI = 3.14159265359;
  static E = 2.71828182846;
  static VERSION = "1.0.0"; // 顺便加个版本号

  static getPiRounded() {
    return Math.round(MathUtils.PI);
  }
}

console.log(MathUtils.PI); // 输出: 3.14159265359
console.log(MathUtils.E);  // 输出: 2.71828182846
console.log(MathUtils.VERSION); // 输出: 1.0.0

console.log(MathUtils.getPiRounded()); //输出: 3

在这个例子中,PIEVERSION 都是 MathUtils 类的 static 属性。你可以直接通过 MathUtils.PIMathUtils.EMathUtils.VERSION 来访问它们,而不需要创建 MathUtils 的实例。getPiRounded 是一个静态方法, 用于返回圆周率的近似整数值。

static 方法:类的工具函数

static 方法通常用于定义一些与类相关的工具函数。这些函数不需要访问类的实例属性,只需要处理一些与类相关的逻辑。

继续用包子铺的例子,我们可以定义一个 Baozi 类,然后定义一个 static 方法来计算所有包子的总成本:

class Baozi {
  constructor(xian, pi) {
    this.xian = xian;
    this.pi = pi;
  }
}

class BaoziShop {
    static baoziCount = 0;
    static baoziPrice = 2; //每个包子2块钱

    static calculateTotalRevenue(baoziSold) {
      return baoziSold * BaoziShop.baoziPrice;
    }

    static addBaozi() {
        BaoziShop.baoziCount++;
    }

    static getBaoziCount() {
        return BaoziShop.baoziCount;
    }

}

BaoziShop.addBaozi();
BaoziShop.addBaozi();
BaoziShop.addBaozi();

console.log(`一共卖了 ${BaoziShop.getBaoziCount()} 个包子,收入 ${BaoziShop.calculateTotalRevenue(BaoziShop.getBaoziCount())} 元`) //一共卖了 3 个包子,收入 6 元

在这个例子中,calculateTotalRevenue 是一个 static 方法。它接收一个参数 baoziSold,表示卖出的包子数量,然后计算总收入。你可以直接通过 BaoziShop.calculateTotalRevenue(100) 来调用这个方法,而不需要创建 BaoziShop 的实例。

static 方法与 this

需要注意的是,在 static 方法中,this 指向的是类本身,而不是类的实例。因此,你不能在 static 方法中访问类的实例属性。

class MyClass {
  constructor(name) {
    this.name = name;
  }

  static myStaticMethod() {
    //console.log(this.name); // TypeError: Cannot read properties of undefined (reading 'name')
    console.log("This is a static method.");
  }
}

MyClass.myStaticMethod(); // 输出: This is a static method.

在这个例子中,如果在 myStaticMethod 中尝试访问 this.name,会报错,因为 this 指向的是 MyClass 类本身,而不是类的实例。

static 块:静态初始化

ES2022 引入了 static 块,它允许你在类定义中执行一些静态初始化操作。这对于一些复杂的静态属性初始化非常有用。

class MyClass {
  static {
    // 在这里执行静态初始化操作
    MyClass.staticProperty = this.calculateInitialValue();
    console.log("Static initialization complete.");
  }

  static calculateInitialValue() {
    // 一些复杂的计算逻辑
    return Math.random();
  }
}

console.log(MyClass.staticProperty); // 输出一个随机数,并显示 "Static initialization complete."

在这个例子中,static 块会在类定义时执行,并初始化 MyClass.staticProperty。这对于一些需要复杂计算才能得到的静态属性非常方便。

static 属性与方法的使用场景

  • 常量和配置信息: 定义一些与类相关的常量或者配置信息,例如数学常量、API 地址、版本号等。
  • 工具函数: 定义一些与类相关的工具函数,例如字符串处理、日期格式化、数据校验等。
  • 单例模式: 使用 static 属性和方法来实现单例模式,保证一个类只有一个实例。
  • 工厂方法: 使用 static 方法来实现工厂方法,根据不同的参数创建不同的对象。

代码示例:单例模式

单例模式是一种常用的设计模式,它保证一个类只有一个实例,并提供一个全局访问点。可以使用 static 属性和方法来实现单例模式。

class Singleton {
  static instance = null;

  constructor() {
    if (Singleton.instance) {
      return Singleton.instance;
    }
    this.data = "Singleton instance";
    Singleton.instance = this;
  }

  getData() {
    return this.data;
  }
}

const instance1 = new Singleton();
const instance2 = new Singleton();

console.log(instance1 === instance2); // 输出: true
console.log(instance1.getData()); // 输出: Singleton instance
console.log(instance2.getData()); // 输出: Singleton instance

在这个例子中,Singleton.instance 是一个 static 属性,用于保存单例实例。构造函数首先检查 Singleton.instance 是否已经存在,如果存在,则直接返回已存在的实例;否则,创建一个新的实例,并将其赋值给 Singleton.instance。这样就保证了 Singleton 类只有一个实例。

代码示例:工厂方法

工厂方法是一种创建型设计模式,它定义一个创建对象的接口,但由子类决定要实例化的类是哪一个。可以使用 static 方法来实现工厂方法。

class Product {
  constructor(name) {
    this.name = name;
  }

  getName() {
    return this.name;
  }
}

class ProductFactory {
  static createProduct(type) {
    switch (type) {
      case "A":
        return new Product("Product A");
      case "B":
        return new Product("Product B");
      default:
        return new Product("Unknown Product");
    }
  }
}

const productA = ProductFactory.createProduct("A");
const productB = ProductFactory.createProduct("B");
const productC = ProductFactory.createProduct("C");

console.log(productA.getName()); // 输出: Product A
console.log(productB.getName()); // 输出: Product B
console.log(productC.getName()); // 输出: Unknown Product

在这个例子中,ProductFactory.createProduct 是一个 static 方法,它根据传入的类型参数创建不同的 Product 对象。这使得创建对象的过程更加灵活,并且可以隐藏对象的创建细节。

静态方法与普通方法的区别

为了更清晰地理解 static 方法和普通方法的区别,我们来看一个表格:

特性 static 方法 普通方法
访问方式 通过类名直接访问(例如:MyClass.myMethod() 通过类的实例访问(例如:instance.myMethod()
this 指向 类本身 类的实例
访问权限 只能访问 static 属性和方法 可以访问 static 和实例属性和方法
使用场景 工具函数、常量、单例模式、工厂方法等 操作实例属性、执行实例相关逻辑等

高级用法:继承中的 static

static 属性和方法也可以被继承。子类可以访问父类的 static 属性和方法,也可以覆盖父类的 static 方法。

class Parent {
  static myStaticProperty = "Parent Static Property";

  static myStaticMethod() {
    console.log("Parent Static Method");
  }
}

class Child extends Parent {
  static myStaticMethod() {
    console.log("Child Static Method");
    super.myStaticMethod(); // 调用父类的 static 方法
  }
}

console.log(Child.myStaticProperty); // 输出: Parent Static Property
Child.myStaticMethod(); // 输出: Child Static Method,然后输出: Parent Static Method

在这个例子中,Child 类继承了 Parent 类,并且覆盖了 myStaticMethod 方法。在 Child.myStaticMethod 中,可以使用 super.myStaticMethod() 来调用父类的 static 方法。

最佳实践

  • 谨慎使用 static 只有在确实需要类级别的属性和方法时才使用 static。过度使用 static 可能会导致代码难以测试和维护。
  • 避免在 static 方法中访问实例属性: static 方法不应该依赖于类的实例状态。
  • 使用 static 块进行复杂的静态初始化: 对于一些需要复杂计算才能得到的静态属性,可以使用 static 块进行初始化。
  • 合理利用继承中的 static 在继承中,可以利用 static 属性和方法来实现一些通用的逻辑。

总结

static 属性和方法是 JavaScript 中非常有用的特性,它们允许你定义类级别的工具函数和常量。合理使用 static 可以使你的代码更加简洁、高效和易于维护。记住,static 就像类里面的老干部,不属于任何具体的实例,而是属于类本身,专门负责处理一些类级别的任务。

好了,今天的讲座就到这里。希望大家能够掌握 static 属性和方法的使用,并在实际开发中灵活运用。如果大家还有什么问题,欢迎随时提问。下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注