如何在 Javascript 中创建静态变量?
JavaScript 中的静态变量
如果您来自基于类的、静态类型的面向对象语言(如 Java、C++ 或 C#),我假设您正在尝试创建与“类型”相关联的变量或方法,而不是与实例相关联。
使用“经典”方法和构造函数的示例可能可以帮助您了解基本 OO JavaScript 的概念:
function MyClass () { // constructor function
var privateVariable = "foo"; // Private variable
this.publicVariable = "bar"; // Public variable
this.privilegedMethod = function () { // Public Method
alert(privateVariable);
};
}
// Instance method will be available to all instances but only load once in memory
MyClass.prototype.publicMethod = function () {
alert(this.publicVariable);
};
// Static variable shared by all instances
MyClass.staticProperty = "baz";
var myInstance = new MyClass();
staticProperty
在 MyClass 对象(它是一个函数)中定义并且与其创建的实例无关,JavaScript 将函数视为一等对象,因此作为一个对象,您可以为函数分配属性。
更新: ES6 引入了通过关键字声明类的能力class
。它是现有基于原型的继承的语法糖。
该static
关键字允许您轻松地在一个类中定义的静态属性或方法。
让我们看看上面用 ES6 类实现的例子:
class MyClass {
// class constructor, equivalent to
// the function body of a constructor
constructor() {
const privateVariable = 'private value'; // Private variable at the constructor scope
this.publicVariable = 'public value'; // Public property
this.privilegedMethod = function() {
// Public Method with access to the constructor scope variables
console.log(privateVariable);
};
}
// Prototype methods:
publicMethod() {
console.log(this.publicVariable);
}
// Static properties shared by all instances
static staticProperty = 'static value';
static staticMethod() {
console.log(this.staticProperty);
}
}
// We can add properties to the class prototype
MyClass.prototype.additionalMethod = function() {
console.log(this.publicVariable);
};
var myInstance = new MyClass();
myInstance.publicMethod(); // "public value"
myInstance.additionalMethod(); // "public value"
myInstance.privilegedMethod(); // "private value"
MyClass.staticMethod(); // "static value"
您可能会利用 JS 函数也是对象这一事实——这意味着它们可以具有属性。
例如,引用(现已消失)文章静态变量在 Javascript 中给出的示例:
function countMyself() {
// Check to see if the counter has been initialized
if ( typeof countMyself.counter == 'undefined' ) {
// It has not... perform the initialization
countMyself.counter = 0;
}
// Do something stupid to indicate the value
alert(++countMyself.counter);
}
如果您多次调用该函数,您将看到计数器正在增加。
这可能是比用全局变量污染全局命名空间更好的解决方案。
这是另一种可能的解决方案,基于闭包:Trick to use static variables in javascript:
var uniqueID = (function() {
var id = 0; // This is the private persistent value
// The outer function returns a nested function that has access
// to the persistent value. It is this nested function we're storing
// in the variable uniqueID above.
return function() { return id++; }; // Return and increment
})(); // Invoke the outer function after defining it.
这会给你带来同样的结果——除了这次返回的是递增的值,而不是显示出来。
您可以通过 IIFE(立即调用的函数表达式)执行此操作:
var incr = (function () {
var i = 1;
return function () {
return i++;
}
})();
incr(); // returns 1
incr(); // returns 2
我看过几个类似的答案,但我想提一下,这篇文章描述得最好,所以我想与你分享。
这是从中提取的一些代码,我对其进行了修改以获得一个完整的示例,希望它可以为社区带来好处,因为它可以用作类的设计模板。
它还回答了您的问题:
function Podcast() {
// private variables
var _somePrivateVariable = 123;
// object properties (read/write)
this.title = 'Astronomy Cast';
this.description = 'A fact-based journey through the galaxy.';
this.link = 'http://www.astronomycast.com';
// for read access to _somePrivateVariable via immutableProp
this.immutableProp = function() {
return _somePrivateVariable;
}
// object function
this.toString = function() {
return 'Title: ' + this.title;
}
};
// static property
Podcast.FILE_EXTENSION = 'mp3';
// static function
Podcast.download = function(podcast) {
console.log('Downloading ' + podcast + ' ...');
};
鉴于该示例,您可以按如下方式访问静态属性/函数:
// access static properties/functions
console.log(Podcast.FILE_EXTENSION); // 'mp3'
Podcast.download('Astronomy cast'); // 'Downloading Astronomy cast ...'
并且对象属性/功能简单地为:
// access object properties/functions
var podcast = new Podcast();
podcast.title = 'The Simpsons';
console.log(podcast.toString()); // Title: The Simpsons
console.log(podcast.immutableProp()); // 123
请注意,在 podcast.immutableProp() 中,我们有一个闭包:对 _somePrivateVariable 的引用保存在函数内部。
您甚至可以定义getter 和 setter。看看这个代码片段(d
你想为其声明属性的对象的原型在哪里,y
是一个在构造函数之外不可见的私有变量):
// getters and setters
var d = Date.prototype;
Object.defineProperty(d, "year", {
get: function() {return this.getFullYear() },
set: function(y) { this.setFullYear(y) }
});
它定义了属性d.year
viaget
和set
functions - 如果您不指定set
,则该属性是只读的且无法修改(请注意,如果您尝试设置它,则不会出现错误,但它无效)。每个属性都有属性writable
,configurable
(允许在声明后更改)和enumerable
(允许将其用作枚举数),它们是默认的false
。您可以通过defineProperty
在第三个参数中设置它们,例如enumerable: true
。
同样有效的是以下语法:
// getters and setters - alternative syntax
var obj = { a: 7,
get b() {return this.a + 1;},
set c(x) {this.a = x / 2}
};
它定义了可读/可写属性a
、只读属性b
和只写属性c
,通过这些属性a
可以访问属性。
用法:
console.log(obj.a); console.log(obj.b); // output: 7, 8
obj.c=40;
console.log(obj.a); console.log(obj.b); // output: 20, 21
笔记:
为了避免在您忘记new
关键字时出现意外行为,我建议您将以下内容添加到函数中Podcast
:
// instantiation helper
function Podcast() {
if(false === (this instanceof Podcast)) {
return new Podcast();
}
// [... same as above ...]
};
现在以下两个实例都将按预期工作:
var podcast = new Podcast(); // normal usage, still allowed
var podcast = Podcast(); // you can omit the new keyword because of the helper
'new' 语句创建一个新对象并复制所有属性和方法,即
var a=new Podcast();
var b=new Podcast();
a.title="a"; b.title="An "+b.title;
console.log(a.title); // "a"
console.log(b.title); // "An Astronomy Cast"
另请注意,在某些情况下,使用return
构造函数中的语句Podcast
返回自定义对象保护类内部依赖但需要公开的函数可能很有用。这将在系列文章的第 2 章(对象)中进一步解释。
你可以这么说a
并b
继承自Podcast
. 现在,如果你想要添加到播客的方法适用于所有的人后a
,并b
已实例化?在这种情况下,请.prototype
按如下方式使用:
Podcast.prototype.titleAndLink = function() {
return this.title + " [" + this.link + "]";
};
现在打电话a
和b
再次:
console.log(a.titleAndLink()); // "a [http://www.astronomycast.com]"
console.log(b.titleAndLink()); // "An Astronomy Cast [http://www.astronomycast.com]"
您可以在此处找到有关原型的更多详细信息。如果你想做更多的继承,我建议你看看这个。
本系列文章中,我上面提到的强烈建议阅读,还包括以下主题:
- 职能
- 对象
- 原型
- 在构造函数上强制执行 New
- 吊装
- 自动分号插入
- 静态属性和方法
请注意,JavaScript的自动分号插入“功能”(如第 6 节中提到的)通常是导致代码中出现奇怪问题的原因。因此,我宁愿将其视为错误而不是功能。
如果你想阅读更多,这里有一篇关于这些主题的非常有趣的MSDN 文章,其中一些描述提供了更多细节。
什么是有趣的阅读,以及(也涵盖的议题上面提到的)是从这些文章MDN JavaScript的指南:
如果您想知道如何在 JavaScript 中模拟 c#out
参数(如 in DateTime.TryParse(str, out result)
),您可以在此处找到示例代码。
那些使用 IE 的人(它没有用于 JavaScript 的控制台,除非您使用打开开发人员工具F12并打开控制台选项卡)可能会发现以下代码片段很有用。它允许您console.log(msg);
像上面示例中使用的那样使用。只需在Podcast
函数之前插入它。
为方便起见,以下是一个完整的代码片段中的上述代码:
笔记:
您可以在这里(JavaScript 最佳实践)和那里('var' 与 'let')找到一些关于 JavaScript 编程的一般提示、提示和建议。还推荐这篇 关于隐式类型转换(强制)的文章。
一种使用类并将它们编译成 JavaScript 的便捷方法是TypeScript。 这是一个操场,您可以在其中找到一些向您展示其工作原理的示例。即使您目前没有使用 TypeScript,也可以查看一下,因为您可以在并排视图中比较 TypeScript 和 JavaScript 结果。大多数示例都很简单,但也有一个 Raytracer 示例,您可以立即试用。我特别推荐通过在组合框中选择“使用类”、“使用继承”和“使用泛型”示例来查看它们 - 这些是您可以立即在 JavaScript 中使用的不错的模板。typescript与Angular一起使用。
为了在 JavaScript 中实现局部变量、函数等的封装,我建议使用如下模式(JQuery 使用相同的技术):
<html>
<head></head>
<body><script>
'use strict';
// module pattern (self invoked function)
const myModule = (function(context) {
// to allow replacement of the function, use 'var' otherwise keep 'const'
// put variables and function with local module scope here:
var print = function(str) {
if (str !== undefined) context.document.write(str);
context.document.write("<br/><br/>");
return;
}
// ... more variables ...
// main method
var _main = function(title) {
if (title !== undefined) print(title);
print("<b>last modified: </b>" + context.document.lastModified + "<br/>");
// ... more code ...
}
// public methods
return {
Main: _main
// ... more public methods, properties ...
};
})(this);
// use module
myModule.Main("<b>Module demo</b>");
</script></body>
</html>
当然,您可以 - 并且应该 - 将脚本代码放在一个单独的*.js
文件中;这只是内联编写以保持示例简短。
自调用函数(也称为 IIFE = 立即调用函数表达式)在此处进行了更详细的描述。
您可以使用 arguments.callee 来存储“静态”变量(这在匿名函数中也很有用):
function () {
arguments.callee.myStaticVar = arguments.callee.myStaticVar || 1;
arguments.callee.myStaticVar++;
alert(arguments.callee.myStaticVar);
}