JS 静态方法 (`static`) 与静态属性:定义类级别的行为和数据

嘿,各位程序猿、媛们,大家好!今天咱们来聊聊 JavaScript 里那些“静态”的小伙伴们——静态方法和静态属性。别被“静态”俩字吓到,其实它们就像类里的 VIP 成员,有着一些特殊的待遇和用途。

开场白:类里的“特殊嘉宾”

想象一下,你开了一家咖啡馆(也就是你的类),普通的顾客(实例)来了,你得为他们每个人都煮咖啡、加糖、送餐巾纸。但有些 VIP 嘉宾(静态成员)来了,他们可能不需要咖啡,只是想看看你的咖啡豆产地,或者想了解一下咖啡馆的经营理念。这些信息,不需要针对每一个顾客提供,而是咖啡馆本身就具备的。这就是静态方法和静态属性的意义所在。

第一幕:静态属性——类级别的“公共财产”

静态属性是属于类本身的属性,而不是属于类的实例。这意味着,无论你创建多少个类的实例,静态属性只有一个副本,所有实例共享这个副本。把它想象成咖啡馆的地址,无论你点多少杯咖啡,咖啡馆的地址永远只有一个。

语法:

class CoffeeShop {
  static address = "星巴克大街1号"; // 静态属性

  constructor(name) {
    this.name = name; //实例属性
  }

  getShopInfo() {
    return `咖啡馆名字:${this.name},地址:${CoffeeShop.address}`; //访问静态属性要用类名
  }
}

const shop1 = new CoffeeShop("小明咖啡");
const shop2 = new CoffeeShop("老王咖啡");

console.log(shop1.getShopInfo()); // 输出:咖啡馆名字:小明咖啡,地址:星巴克大街1号
console.log(shop2.getShopInfo()); // 输出:咖啡馆名字:老王咖啡,地址:星巴克大街1号

CoffeeShop.address = "星巴克大街2号"; // 修改静态属性

console.log(shop1.getShopInfo()); // 输出:咖啡馆名字:小明咖啡,地址:星巴克大街2号
console.log(shop2.getShopInfo()); // 输出:咖啡馆名字:老王咖啡,地址:星巴克大街2号

代码解读:

  • static address = "星巴克大街1号"; 定义了一个静态属性 address,它的值是咖啡馆的地址。
  • this.name = name; 定义了一个实例属性 name,每个咖啡馆实例都有自己的名字。
  • CoffeeShop.address 通过类名访问静态属性,而不是通过实例。
  • 修改 CoffeeShop.address 会影响所有实例的 getShopInfo 方法的输出,因为它们共享同一个 address

使用场景:

  • 常量: 存放一些不会改变的常量值,例如数学上的圆周率、物理上的光速等。
  • 配置信息: 存放一些全局的配置信息,例如API的根地址、应用的名称等。
  • 计数器: 统计类的实例数量。
  • 缓存: 缓存一些计算结果,避免重复计算。

例子:统计咖啡馆数量

class CoffeeShop {
  static shopCount = 0; // 静态属性,记录咖啡馆数量

  constructor(name) {
    this.name = name;
    CoffeeShop.shopCount++; // 每次创建实例,数量加1
  }

  static getShopCount() {
    return `目前共有 ${CoffeeShop.shopCount} 家咖啡馆`;
  }
}

const shop1 = new CoffeeShop("小明咖啡");
const shop2 = new CoffeeShop("老王咖啡");

console.log(CoffeeShop.getShopCount()); // 输出:目前共有 2 家咖啡馆

第二幕:静态方法——类级别的“工具函数”

静态方法是属于类本身的方法,而不是属于类的实例。这意味着,你不需要创建类的实例就可以调用静态方法。把它想象成咖啡馆的宣传语,不需要你买咖啡,就可以看到。

语法:

class CoffeeShop {
  static isValidName(name) { // 静态方法
    return name && name.length > 2;
  }

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

  getName() {
    return this.name;
  }
}

console.log(CoffeeShop.isValidName("小明咖啡")); // 输出:true
console.log(CoffeeShop.isValidName("小")); // 输出:false

const shop1 = new CoffeeShop("小明咖啡");
// console.log(shop1.isValidName("小明咖啡")); // 报错:shop1.isValidName is not a function,实例不能调用静态方法
console.log(shop1.getName());

代码解读:

  • static isValidName(name) 定义了一个静态方法 isValidName,用于判断咖啡馆名字是否有效。
  • 通过类名 CoffeeShop 直接调用静态方法 isValidName,不需要创建实例。
  • 实例 shop1 不能直接调用静态方法 isValidName,会报错。

使用场景:

  • 工具函数: 提供一些通用的工具函数,例如字符串处理、日期格式化、数学计算等。
  • 工厂方法: 用于创建类的实例,例如根据不同的参数创建不同类型的实例。
  • 辅助函数: 辅助类的其他方法完成一些特定的任务。

例子:格式化咖啡豆产地信息

class CoffeeBean {
  constructor(origin, roastLevel) {
    this.origin = origin;
    this.roastLevel = roastLevel;
  }

  static formatOrigin(origin) {
    return origin.toUpperCase();
  }

  getOriginInfo() {
    return `产地:${CoffeeBean.formatOrigin(this.origin)}, 烘焙程度:${this.roastLevel}`;
  }
}

const bean1 = new CoffeeBean("哥伦比亚", "中度烘焙");
console.log(bean1.getOriginInfo()); // 输出:产地:哥伦比亚, 烘焙程度:中度烘焙,注意产地被格式化成大写了

第三幕:静态方法中的 this

在静态方法中,this 指向的是类本身,而不是类的实例。这意味着,你不能在静态方法中访问实例属性和实例方法。

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

  static getShopName() {
    // return this.name; // 报错:Cannot read properties of undefined (reading 'name')
    return "咖啡馆";
  }
}

console.log(CoffeeShop.getShopName()); // 输出:咖啡馆

代码解读:

  • 在静态方法 getShopName 中,this 指向的是 CoffeeShop 类本身,而不是类的实例。
  • 因为类本身没有 name 属性,所以访问 this.name 会报错。

第四幕:静态属性和静态方法的继承

静态属性和静态方法是可以被子类继承的。

class Beverage {
    static category = "饮料";

    static getDescription() {
        return `这是一个${Beverage.category}类`;
    }
}

class Coffee extends Beverage {
    static origin = "埃塞俄比亚";

    static getOriginDescription() {
        return `${Beverage.getDescription()},产地:${Coffee.origin}`;
    }
}

console.log(Coffee.category); // 输出:饮料,继承自 Beverage
console.log(Coffee.getDescription()); // 输出:这是一个饮料类,继承自 Beverage
console.log(Coffee.getOriginDescription()); // 输出:这是一个饮料类,产地:埃塞俄比亚

代码解读:

  • Coffee 类继承了 Beverage 类的静态属性 category 和静态方法 getDescription
  • Coffee 类可以访问和使用 Beverage 类的静态属性和静态方法。
  • Coffee.getOriginDescription() 中,Coffee 既能访问到父类的静态方法和属性,也能访问到子类自己的静态属性。

表格总结:静态属性 vs 实例属性、静态方法 vs 实例方法

特性 静态属性/方法 实例属性/方法
所属 类本身 类的实例
访问方式 类名.属性名/方法名 实例.属性名/方法名
数量 一个类只有一个副本 每个实例都有自己的副本
用途 存储类级别的常量、配置信息、工具函数等 存储实例级别的数据、实现实例级别的行为
this 指向 类本身 类的实例
继承 可以被子类继承 可以被子类继承和重写

第五幕:更深入的理解和使用

  • 单例模式: 静态属性可以用来实现单例模式,保证一个类只有一个实例。
class Singleton {
  static instance = null;

  constructor() {
    if (Singleton.instance) {
      return Singleton.instance; // 如果已经有实例,直接返回
    }
    Singleton.instance = this; // 创建实例
  }

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

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

console.log(instance1 === instance2); // 输出:true
console.log(Singleton.getInstance() === instance1); // 输出:true
  • 命名空间: 静态属性和静态方法可以用来模拟命名空间,避免全局变量污染。
const MyNamespace = {
  VERSION: "1.0",
  add: function(a, b) {
    return a + b;
  }
};

console.log(MyNamespace.VERSION); // 输出:1.0
console.log(MyNamespace.add(1, 2)); // 输出:3

// ES6 以上可以这样写
class MyNamespace {
    static VERSION = "1.0";

    static add(a, b) {
        return a + b;
    }
}

console.log(MyNamespace.VERSION); // 输出:1.0
console.log(MyNamespace.add(1, 2)); // 输出:3

第六幕:注意事项

  • 不要滥用静态成员: 静态成员适用于那些与类本身相关,而不是与类的实例相关的数据和行为。
  • 注意 this 的指向: 在静态方法中,this 指向的是类本身,而不是类的实例。
  • 静态成员不能访问实例成员: 静态成员不能访问实例属性和实例方法。

总结陈词:静态成员的魅力

静态属性和静态方法是 JavaScript 类中非常有用的特性,它们可以帮助你更好地组织代码,提高代码的可读性和可维护性。掌握了静态成员,你就可以像咖啡馆老板一样,更好地管理你的类和对象,提供更优质的服务。希望今天的讲座对大家有所帮助!下次再见!

发表回复

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