JavaScript 中的静态变量

IT技术 javascript variables static closures
2021-01-24 14:06:24

如何在 Javascript 中创建静态变量?

6个回答

如果您来自基于类的、静态类型的面向对象语言(如 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"

不能this.constructor用于从“实例方法”访问静态变量?如果是,则值得将其添加到答案中。
2021-03-11 14:06:24
也许它应该阅读MyClass.prototype.staticProperty = "baz";或更正确的面向对象原则,静态属性实际上应该定义为匿名函数MyClass.prototype.staticProperty = function () {return staticVar;},以便所有实例访问一个也可以用 setter 更改的单个变量。
2021-03-14 14:06:24
您还可以在示例中提及静态函数
2021-03-17 14:06:24
大概privilegedMethod不等同于 OO 中的私有方法,因为它似乎可以在 MyClass 的实例上调用?你的意思是它有特权,因为它可以访问privateVariable
2021-03-20 14:06:24
嗨,我不确定我是否同意这一行 // 所有实例共享的静态变量 'MyClass.staticProperty = "baz";' 我来说,推断你可以从“myInstance.staticProperty”中找到 baz,当然你不能。
2021-03-23 14:06:24

您可能会利用 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.

这会给你带来同样的结果——除了这次返回的是递增的值,而不是显示出来。

更短更清晰:(function() { var id = 0; function uniqueID() { return id++; }; })();
2021-03-11 14:06:24
@SonySantos 您的测试显示 Firefox 40 相反
2021-03-12 14:06:24
使用===typeof支票否则你会得到一些奇怪的胁迫回事。
2021-03-17 14:06:24
作为一种快捷方式,countMyself.counter = countMyself.counter || initial_value;如果静态变量永远不会是假的(假、0、空或空字符串),你就可以这样做
2021-03-20 14:06:24
闭包中的计数器比 Firefox 中的类要快得多。jsperf.com/static-counter-in-class-vs-in-closure
2021-03-21 14:06:24

您可以通过 IIFE(立即调用的函数表达式)执行此操作:

var incr = (function () {
    var i = 1;

    return function () {
        return i++;
    }
})();

incr(); // returns 1
incr(); // returns 2
我想说这是在 JavaScript 中最惯用的方法。太糟糕了,由于其他方法可能更适合来自其他语言的人,因此它没有得到太多的支持。
2021-03-14 14:06:24
我会改写使用“关闭”而不仅仅是“IIFE”。
2021-03-22 14:06:24
恭喜,绝对是最好的回复,简单就是美丽。即使很明显,我是否可以扩展响应:var incr = (function (delta) { var i = 1; return function (delta) return i+=delta;} })();
2021-03-26 14:06:24
我听说它们称为匿名自调用函数,也称为“ASIF”(“好像”您实际上可以阅读它们。):)
2021-03-29 14:06:24

我看过几个类似的答案,但我想提一下,这篇文章描述得最好,所以我想与你分享。

这是从中提取的一些代码,我对其进行了修改以获得一个完整的示例,希望它可以为社区带来好处,因为它可以用作类的设计模板。

它还回答了您的问题:

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.yearviagetsetfunctions - 如果您不指定set,则该属性是只读的且无法修改(请注意,如果您尝试设置它,则不会出现错误,但它无效)。每个属性都有属性writableconfigurable(允许在声明后更改)和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 章(对象)中进一步解释。

你可以这么说ab继承自Podcast. 现在,如果你想要添加到播客的方法适用于所有的人后a,并b已实例化?在这种情况下,请.prototype按如下方式使用

Podcast.prototype.titleAndLink = function() {
    return this.title + " [" + this.link + "]";
};

现在打电话ab再次:

console.log(a.titleAndLink()); // "a [http://www.astronomycast.com]"
console.log(b.titleAndLink()); // "An Astronomy Cast [http://www.astronomycast.com]"

您可以在此处找到有关原型的更多详细信息如果你想做更多的继承,我建议你看看这个


系列文章中,我上面提到的强烈建议阅读,还包括以下主题:

  1. 职能
  2. 对象
  3. 原型
  4. 在构造函数上强制执行 New
  5. 吊装
  6. 自动分号插入
  7. 静态属性和方法

请注意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:&nbsp;</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);
}
据我所知,与 pascal MARTIN 的方式相比,这种方法有一个(只有一个?)优势:您可以在匿名函数上使用它。这方面的一个例子会很棒
2021-03-14 14:06:24
arguments.callee 已弃用。
2021-03-17 14:06:24
我几乎一直在嘲笑 JS,但callee似乎是一件好事。我想知道他们为什么决定弃用这个黑客......:|
2021-03-28 14:06:24