HOWTO · JavaScript
JavaScript 中的静态变量
本教程介绍了 JavaScript 中的静态变量。
本页内容
JavaScript 可以通过公开的类静态字段、函数对象上的属性,或由闭包保留的局部变量,在多次调用之间保存值。这些方式都能保留状态,但状态的归属位置和访问方式各不相同。若要让整个类共享状态,可以使用类字段;若只需为某个函数保存简单的公开状态,可以使用函数属性;若希望调用方使用该值,却不能直接读取或更改其绑定,则可以使用闭包。
JavaScript 中的静态变量意味着什么
静态变量是关联到函数或类、而非某一次特定调用的状态。JavaScript 没有一种适用于所有函数的通用静态变量声明,而是提供了几种实现方式。static 关键字可在类本身上声明字段或方法。赋给函数的属性也归该函数对象所有。闭包则能让内部函数在外部函数返回后,继续访问一个局部绑定。
这些概念彼此相关,但相应机制并非在所有方面都可以互换。类字段和函数属性可以通过所属的类对象或函数对象访问。闭包可以将绑定保持为私有状态,并且只公开你选择提供的操作。
使用类静态字段保存类级状态
公开的类静态字段属于类,因此所有实例都通过类名访问同一个字段;该字段不会复制到每个实例中。这种语法要求运行环境支持公开的类静态字段。如果运行环境不支持,可以改用下文的函数属性或闭包方式。
运行此示例:
本节中的代码以及显示的标签或输出仍为英文。
class VisitCounter {
static count = 0;
static recordVisit() {
return ++VisitCounter.count;
}
}
console.log(VisitCounter.recordVisit());
console.log(VisitCounter.recordVisit());
console.log(VisitCounter.count);
1
2
2
这两次调用都会更新同一个 VisitCounter.count。由于该字段是公开的,其他代码也可以通过 VisitCounter.count 读取或赋值;如果希望由类本身控制访问,请使用私有静态字段(名称以 # 开头)。静态成员应通过类访问,而不是通过 new VisitCounter() 这样的实例访问。
为函数添加属性
函数是对象,因此命名函数可以拥有一个跨调用保留值的属性。这种方式简洁,适合为某个函数关联计数器或缓存。该属性是公开的:其他代码可以检查或更改 nextId.counter,因此这种方式不提供隐私保护。
本节中的代码以及显示的标签或输出仍为英文。
function nextId() {
nextId.counter = (nextId.counter ?? 0) + 1;
return nextId.counter;
}
console.log(nextId());
console.log(nextId());
console.log(nextId.counter);
1
2
2
构造函数也适用同样的思路。Widget.count 这样的属性属于构造函数,而 widget.name 属于某个实例。每个实例都不会各自获得一份 Widget.count 副本;代码通过 Widget 访问这个共享属性。从状态的归属来看,这类似于类级状态,但 JavaScript 函数属性并不具备某些类系统提供的全部访问控制或类型保证。
使用闭包保存私有状态
闭包使内部函数能够在外部函数执行完毕后,继续使用其中的局部绑定。IIFE(立即调用函数表达式)会立即创建外层作用域,并返回一个能够更新所保留值的操作。由于该绑定本身没有放在公开对象上,外部代码无法直接读取或赋值。
本节中的代码以及显示的标签或输出仍为英文。
const counter = (function () {
let count = 0;
return function () {
return ++count;
};
})();
console.log(counter());
console.log(counter());
1
2
这里,counter 闭包引用了 count;每次调用 counter() 都会递增同一个保留值,因此调用会依次输出 1 和 2。该值是私有的,而 counter 是公开接口。若要创建彼此独立的状态,请分别调用一个创建并返回新闭包的工厂函数;调用这个返回的 counter 只会递增它已有的状态。如果调用方需要重置或查看计数值,可以返回一个带有明确方法的小对象,而不是公开该绑定本身。
为什么应避免使用 arguments.callee
arguments.callee 通过函数的 arguments 对象引用当前正在执行的函数。早期的一些写法会用它给该函数添加属性,效果类似于给命名函数添加属性。这是一种遗留技巧,在严格模式下被禁止,因此应用严格模式时,使用它的代码可能会失败;ECMAScript 模块也包括在内。使用命名函数能让这种关联更加清晰,而闭包无需依赖这个受限属性,就能保存私有状态。新代码应优先采用上文介绍的受支持方式,不要围绕 arguments.callee 构建。