【问题标题】:Need advise/help on getting rid of global variables需要关于摆脱全局变量的建议/帮助
【发布时间】:2015-06-26 06:05:25
【问题描述】:

是的,这是另一个关于全局变量的话题。我已经搜索了很多关于它们的信息。但大多数主题只是关于为什么不使用全局变量,我确信我不应该,我更想知道如何不使用它们,我仍然不确定。

我正在做一个项目,它可以创造奇迹,但我目前使用了大约 50 个全局变量,而且这个数字还在不断上升。现在我已经把东西分成了多个 .js 文件。像 load_chunks.js、load_images.js、render_map.js、player_actions.js、input.js 一样,根据它们的用途来传播功能。而且我已经把所有的全局变量都放在了 settings.js 中。

目前我使用这些全局变量的原因如下:
1.基于加载后变化不大或根本没有变化的其他一些全局变量进行计算。通过这样做一次并将其存储在全局变量中,我不再需要再次重复计算。如果我没有将它存储在(全局)变量中,Javascript 将不得不每秒进行多次计算,在某些情况下甚至每秒数千次。
2. 当很多函数都需要全局变量时。就像有这个世界变量一样,我用它来保存世界外观的数据。这个变量是一个多维数组。例如世界[y][x]。 Load_chunks.js 会向该变量添加更多数据,或者如果您移动得太远,则删除数据。在 render_map.js 中也需要这个变量来创建地图,在 player_action.js 中也需要它来查看您是否可以踩到该特定位置。
3.设置;因此,除非我在脚本中更改它们,否则变量中的数字将保持不变。我没有通过我的脚本,在长时间搜索并思考数字是什么后手动更改数字,而是将该数字放入 1 个变量中,并在我的脚本中多次调用该变量。在某些情况下,其他地方也需要这些变量。

我还想提一下,我不使用类,也许正是因为这个原因,我从来没有使用过全局变量...?

那么我该如何摆脱我的全局变量,还是不应该? 我希望您可以向我展示或为我编写一个脚本示例(或指向它的链接),说明我应该如何做。这是我学习最快的方法。

【问题讨论】:

  • 全局变量表明您的架构可能过于紧密耦合,从而阻碍模块化和轻松重用不同的函数......您可能需要重构,因此不需要/更少需要共享变量......如果适当的话,还可以利用自然变量范围并将这些变量作为闭包放在函数中,子函数可以看到它们
  • @vidit 请reconsider 添加架构标签。你的拒绝原因就是我的例子,标签应该有助于描述问题是关于的,而不仅仅是它包含的内容。
  • 添加了架构标签,不确定是否应该删除其他标签。嗯,当我必须发布一些代码时,我要在哪里做呢?一个新的答案,还是编辑主帖?

标签: javascript variables architecture global


【解决方案1】:

我确信我不应该,我更想知道如何不使用它们,我仍然不确定。

load_chunks.js, load_images.js, render_map.js, player_actions.js, input.js, 强烈暗示程序实现,即您的架构可能有几个功能部分,并且您在这些功能之间传递数据。这就是您的全局变量的来源。

那么我该如何摆脱我的全局变量(...)

要改变这一点,您需要以基于对象或组件的方法来构建系统,即:

  • 通过问题域中的对象封装数据 + 相应的功能,例如有一个 World 对象,其中包含 Avatar、Buildings、Airborne 对象等(或您的项目所涉及的任何内容)。

  • 将问题域的逻辑与视图逻辑分开(例如,使用MVC architecture)

  • 然后,您的项目通过本质上在视图和模型对象之间交换消息来组织视图和模型对象之间的交互。

为了促进这一点,我建议使用这些优秀的框架,或一些适合您的运行时环境的等效框架:

  • requirejs - 将模块封装到全局管理的组件中
  • backbonejs - 拥有高效、经过验证的模型/视图、类/对象模型(实际上是为与 REST 样式的后端一起使用而构建的,但这不是严格要求)

代码结构

通常,我构建我的应用程序,使每个对象组件/模块都有一个 .js 文件。这种意义上的模块包含类定义和相应的集合。模块由requirejs管理,类定义使用backbonejs完成。

希望你能给我看或者给我写一个脚本例子

/* define a class e.g. in someclass.js */
define(['backbone'], function($B) {
    var SomeClass = $B.Model.extend({
       // all instance variables, methods etc. follow
       someVar : value,
       someMethod : function() { ... }, 
    });
    return SomeClass;
});

/* use the class, e.g. in app.js */
require(['someclass'], function(SomeClass) {
   var instance = new SomeClass({ initial model attributes });
   var value = instance.get('attribute');
   instance.someMethod();
   ...
});

【讨论】:

  • 是的,我确实使用其中一些全局变量将数据从一个函数传递到另一个函数。例如 World[y][x],正如我在主帖中所说。还有 Player_New_Y、Player_New_X(我最终可能会将其设为对象)。这些变化很大,并且在许多功能中都需要,从移动到加载块,再到渲染地图。还有一些其他的,比如 Tiles 和 Tilesets,load_images.js 只向它们写入一次信息,之后只有 render_map.js 需要它们。我使用的大多数其他全局变量仅适用于某个文件。
  • 当您说“以基于对象/组件的方法构建系统”时,您的意思是像 C# 中的类吗?如果是这种情况,那么我有一个很好的主意。我还可以制作很多我现在拥有的全局变量,成为该类中只能访问(并且只需要)的变量。所以这可能会减少大约 80% 的全局变量。如果我确实需要多个函数中的某些变量(如 World),我只需将其作为对象返回,就像类中的函数一样。还是我错了?
  • @Verkade89 你的意思是像 C# 中的类 - 是的,和许多其他语言一样(如果你知道如何在 C# 中做到这一点,请查看例如 requirejs 和骨干js——你会发现类似的概念)。从你的 cmets 的其他部分,我认为你已经理解了这个想法,你只需要将它应用到你的 Javascript 代码中。
  • 是的,我只是测试了一下。效果很好。我也不相信我需要投入大量工作来以这种方式更改我的代码。好吧,我需要更改变量名称,所以让我们说从 World 到 Load_Chunks.World 或者我称之为包含 World 变量的类。是否有特定的偏好放置变量。我的意思是我可以将 World 放在 load_chunks 类中,也可以放在 render_map 类中。 load_chunks 向 World 写入了很多内容,而 render_map 从世界中读取了很多内容。 render_map 充分利用了世界,每秒几千次。还是没关系?
【解决方案2】:

将变量放入函数闭包中

消除全局变量的一种常见方法是放在函数闭包内:

(function() {
     var declareYourFormerGlobalHere = 0;

     // put all code that uses the variable inside this closure
     // the variable persists for this code to use, but is not actually global
})();

以下是一些用法示例:

// example keeping track of a running count and a cached DOM element
(function() {
    var cntr = 0, obj = document.getElementById("test");
    setInterval(function() {
        ++cntr;
        if (obj.value < cntr) {
            // do something
        } else {
            // do something else
        }
    }, 1000);
})();

// example keeping track of a time of last click
(function() {
    var timeOfLastClick = 0;
    document.getElementById("run").addEventListener("click", function(e) {
        var now = new Date().getTime();
        // only process click if more than 2 seconds have passed since last click
        if (now - timeOfLastClick > 2000) {
            // OK to process the click
        }
        timeOfLastClick = now;
    });
})();

有时,您实际上可以像这样将所有代码或几乎所有代码包含在一个闭包中,并且您当前的所有全局变量都成为闭包内的局部变量,而不是实际的全局变量。 jQuery 使用这种技术来声明它使用的许多持久变量,这些变量都不是真正的全局范围。


使用单个命名空间对象

另一种减少全局变量数量的常用方法是使用命名空间概念。在这个概念中,您声明一个全局对象并将其他持久变量作为这个全局对象的属性。这仍然为您留下了一个全局变量,您可以在这个全局变量中拥有任意数量的属性。

var myNamepaceObject = {};
myNamespaceObject.var1 = "foo";    
myNamespaceObject.var2 = "whatever";

这种技术也被 jQuery 使用,因为 jQuery 提供的所有全局可访问的函数都可以从 jQuery 对象中获得,例如 jQuery.extend() 或 jQuery.contains()。 jQuery 公开了一个全局变量,然后许多其他全局可访问的函数可用作该单个全局对象的属性。


模块模式

通常所说的“模块模式”结合了上述两种技术,其中您有一个同时使用属性和闭包变量的模块变量。

var MODULE = (function () {
    var my = {},
        privateVariable = 1;

    function privateMethod() {
        // ...
    }

    my.moduleProperty = 1;
    my.moduleMethod = function () {
        // ...
    };

    return my;
}());

您可以在这些参考资料中看到对此设计模式的讨论:

JavaScript Module Pattern: In-Depth

Learning Javascript - The Module Pattern

Mastering the Module Pattern

JavaScript module pattern with example

【讨论】:

  • 添加了更多示例和参考。
  • 查看我对 mohamed-stark 帖子的回复,了解我对函数闭包和命名空间对象的回答。关于模块模式,我正在考虑类似的事情,我只是不知道该怎么做。它看起来很像使用类,我对它很陌生。只是不确定它的性能如何,我担心(像往常一样),但我会在 jsperf.com 上进行测试。还必须检查我是否会遇到任何严重的问题。在某些情况下,我需要以正确的顺序放置/运行变量。
  • @Verkade89 - 您无需担心任何这些模式的性能问题。闭包中的变量实际上比全局变量执行得更快(在解释器查找路径中,它们比全局变量更早被发现)。你对 mohamed-stark 的评论并没有真正描述你做得很好,让我明白你为什么不能使用闭包解决方案。如果您想要关于您的特定代码的具体建议,那么您将必须包含您尝试删除的全局变量的实际代码示例,我们可以非常具体并为您提供可以直接使用的代码。
  • @Verkade89 - 你不应该害怕在 Javascript 中使用对象。它非常值得学习和使用,只有当你这样做时,你才真正开始使用 Javascript 的真正力量。似乎(根据您的描述)您的代码也非常程序化,并且转换为基于对象的设计通常还可以让您将状态变量放入对象实例数据而不是全局变量中。但是,如何做到这一点对于您正在编写的代码非常具体,因此详细的建议需要查看您的实际代码。
  • 再次查看闭包解决方案后,我认为确实有可能。如果我能让它工作,我将不得不对其进行测试。我需要从 body-tag 中删除事件。因为那些事件会直接触发某个函数,而那些函数在闭包函数内部找不到变量。所以必须使用 addEventListener,我已经部分使用了触摸屏。反正我是这么想的,可能我错了。我将很快进行一些测试,以更好地了解并了解其工作原理。
【解决方案3】:

我之前也遇到过同样的问题,但这是个大问题,因为我们有第三方 javascript 代码,而且我们的代码存在于客户网站中,所以我们应该找到一种方法来省略全局变量的出现,简而言之: 使用 1 个全局变量命名空间,并使所有其他变量成为它的属性:

GlobalVariable = {}
GlobalVariable.var1 = "var1"
GlobalVariable.var2 = "var2"

您还可以使其更有用并将其分组:

 GlobalVariable.helper = {}
 GlobalVariable.helper.var1 = "var1"
 GlobalVariable.helper.var2 = "var2"

因此您可以在不同的组下使用相同的名称。

如果你想完全省略它,你可以使用:

(function(document, window) {
    // all variables here is global only inside the scope
}(document, window));

【讨论】:

  • 第一个对我来说是可以理解的,我也想过做这样的事情。只需 1 个变量,然后将其设为对象。通过在其中添加更多对象进行分类。我只是不确定这是否是正确的方法。我认为带有 (function(){ }()) 的那个不会起作用,或者至少它会很狡猾。因为这将是我的主要功能,它必须加载“设置”变量,我现在每次都在全局范围内,并且在那里我必须调用将处理所有内容以渲染地图的函数,我相信。
  • 是的,范围会限制你,如果你使用全局变量跨多个文件将很难处理,但是对于使用名称空间它解决了有很多变量的混乱问题,正如我所说对于客户可以接受的第三方应用,在他们的网站中有 1 个全局变量,但对于你的应用来说是唯一的。
  • 这基本上只是将全局变量从文档/窗口范围移动到 GlobalVariable 对象中。但是,它并没有摆脱共享状态的问题,据我所知,这是 OP 的主要目标。
猜你喜欢
  • 2015-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-26
  • 1970-01-01
  • 2021-02-11
  • 2020-05-19
相关资源
最近更新 更多