嘿,各位程序猿、媛们,大家好!今天咱们来聊聊 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 类中非常有用的特性,它们可以帮助你更好地组织代码,提高代码的可读性和可维护性。掌握了静态成员,你就可以像咖啡馆老板一样,更好地管理你的类和对象,提供更优质的服务。希望今天的讲座对大家有所帮助!下次再见!