什么是“泄漏”到全球范围?

不久之前,我提出了一个JavaScript设计模式(模块模式 – 见下文),我从John Resig的例子中得到了一个解决某人问题的一部分 ,我收到了以下评论:

“……这种模式有点过于设计而不是那么好。 仍然渗透到全球范围。 而你不打开自己的异步装载机。 但它比ad-hoc编码更好!“

所以…

如果“泄漏”到全局范围意味着“您的对象被附加到浏览器窗口(对象)”……那么所有内容都已经被追加(全局):

这“泄漏”到全球范围:

window.jQuery 

…只需调用: window.jQuery并将其解析为函数();

这“泄漏”到全球范围:

 function HelloWorld() { alert('Howdy'); } 

…只需致电: window.HelloWorld() ,你就会得到’你好’。

这“泄漏”到全球范围:

 var myVariable = 10; 

…只需致电: window.myVariable ,你就会得到10分

如果评论者是正确的,那么以上所有“泄漏”到全球范围。 所以,就个人而言,我没有看到一种不“泄漏”到全局范围内的方法,因为即使你的表单控件也存在(也是如此)。

因此,这是我的问题……

  • 什么是“泄漏”到全球范围?
  • 为什么那么糟糕?
  • 你怎么避免它?
  • 当想要创建持久的自定义对象时,为什么模块模式(下面)不好?
  • 设计模式让你封装复杂的逻辑, 封装突然变坏只是因为我们用JavaScript编写
  • 或者……这个评论者完全错了吗?

这是我上面提到的模块模式:

  var myNamespace = (function($) { var publicInstances = {}; // *********************** // myObject publicInstances.myObject = myObject; function myObject() { /// A pointer to this var self = this; this.someProperty = new String(); this.initialize = function() { /// your code here } this.someMethod = function() { /// your code here } self.initialize(); } return publicInstances; })(jQuery); jQuery(document).ready(function() { // Use would look like var myInstance = new myNamespace.myObject(); });  

更新
我对以下答案感到满意,并感谢大家花时间发表评论。

收回以下答案:
当局部范围中使用的内容无意中可用于全局范围(例如窗口对象)时,会发生“泄漏”到全局范围。 这很糟糕,因为它会打开页面以查找可能导致变量解析为意外值或类型的潜在命名冲突。

故意使变量全局化不被视为“泄漏”。 但是,需要正确命名对象,以减少所述命名冲突的可能性。

您无法避免全局范围的变量,但您可以通过使用异步加载器和在RequireJSCurl等插件中提供的定义模块来降低上述风险。

[[短篇故事]]

不要使用全局变量并使用像requirejs或curl这样的异步模块加载器

[[很长的故事]]

该评论结构不合理。

模块系统没有任何问题。 我一直在抱怨使用全局变量。 (我仍然认为完整的通用模块模式是膨胀的)。

是否应该避免所有全局变量是一个不同的问题,我认为这是一个风格问题。 您可以使用异步加载器传递模块或使用window传递模块。

  • 什么是“泄漏”到全球范围?

我的意思是你创建的全局变量。 最小化全局变量的使用是一种模式。 在函数式编程中,可以使零全局变量,但这与使用全局模块不同。

  • 为什么那么糟糕?

全局拥有任何状态都可能导致该状态被破坏。

  • 你怎么避免它?

你不能。 您可以最小化全局变量的数量。 要避免完全处于全局状态,可以使用异步加载器。 这些为您定义了一些全局变量,然后您可以使用它们。

  • 当想要创建持久的自定义对象时,为什么模块模式(下面)不好?

模块模式没有任何问题。 问题是全局存储您的模块。 问题是拥有全局命名空间。

  • 设计模式让你封装复杂的逻辑,封装突然变坏只是因为我们用JavaScript编写?

现在我已经清除了评论的意图,这个问题并不真正相关

  • 或者……这个评论者完全错了吗?

该评论充其量只是措辞不当。 我反对全局命名空间而不是模块,但没有恰当地说明这一点。

另一种方法是使用异步加载器和定义模块。 这些可以缩小到两个全局变量。 definerequire

require = function(moduleName, callback)

这将获得一个模块,然后将其返回给您。

define = function(obj)

这定义了一个模块。

这里的概念是您的多文件代码如下:

 // main.js require([ "foo.js", "bar.js", ..., ], function(foo, bar, ...) { // do stuff }); //foo.js (function() { var namespace = modulePatternCode; ... define(namespace): })(); //bar.js (function() { var namespace = modulePatternCode; ... define(namespace): })(); 

“泄漏”到全球范围是指本地范围内使用的内容无意中可用于全局范围。 这意味着分配给当前范围中尚未定义的变量:

 function myFunction() { a=1; } myFunction(); alert(a); //-> 1 

这很糟糕,因为可能存在命名冲突,导致变量具有与预期不同的值/类型。 当您忘记对for语句中使用的变量使用var关键字时,它也可能导致旧Internet Explorer中的错误。

我不打算故意将变量全局变为“泄漏”,因为它更像是将它“倾注”到全球范围内。 然而,这仍然被一些人认为是不好的做法(尽管我认为这有点戏剧性),因为仍然存在与window对象的当前属性或其他脚本和库设置的变量的潜在命名冲突。

您的模块只会“泄漏”它的命名空间持有者,所以它是非常可接受的。

使用RequireJS的 Loader示例:

在utils.js中定义实用程序模块:

 define(function () { return { each: function (iterable, callback) { // ... }, map: function (iterable, mapper) { // ... } }; }); 

在另一个模块中使用上面的模块,比如math.js:

 define([ "utils" ], function (utils) { return { sum: function (numbers) { var sum = 0; utils.each(numbers, function (n) { sum += n; }); return sum; }, average: function (numbers) { return this.sum(numbers) / numbers.length; } }; }); 

你可以在另一个文件中使用math.js,比如main.js:

 console.log("About to add 1-3"); require([ "math" ], function (math) { console.log(math.sum([ 1, 2, 3 ])); }); 

您仍然可以拥有名称空间,并且仍然可以在模块内保持温暖和舒适:

namespace.js:

 define([ "foo", "bar", "moo" ] function (foo, bar, moo) { return { foo: foo, bar: bar, moo: moo }; }); 

然后其余模块可以在定义期间使用此命名空间:

 define([ "namespace" ], function (namespace) { namespace.foo(42); }); 

或者在运行时,在其他一些模块中:

 define(function () { return { initialize: function () { require([ "namespace" ], function (namespace) { namespace.foo(42); }); } }; }); 

在上面的用法中,除了definerequire之外,只有全局性的。 当然,这些仅仅是说明性示例,因为RequireJS中有许多不同类型的定义/使用模块。