【问题标题】:Point of module pattern?模块模式的要点?
【发布时间】:2016-08-27 16:41:23
【问题描述】:

模块的主要好处(我听说过)是它们隐藏了私有变量。

var Module = (function() {
     var privateProperty = 'foo';

     return {
         publicProperty: '...',
         publicMethod: function(args) { ... }
     }
})();

但 IIFE 不是必须的。如果你只是删除它,privateProperty 无论如何都会被隐藏。那么为什么要使用 IIFE?我正在尝试理解其中的原理。

编辑:

我一直在读到,没有 IIFE 的 privateProperty 将成为全局范围的一部分。我认为这是错误的。

如果我执行以下操作:

console.log(privateProperty); // => Reference Error: privateProperty isn't defined

我收到一个参考错误。如果我这样做:

console.log(Module.privateProperty) // => undefined

我不确定。如果我这样做:

var mod = new Module();
console.log(mod.privateProperty); // => undefined

我不确定。全局作用域无法访问 JS 中的局部作用域。

编辑 2:

test.js

var test = (function() {
  var privateProperty = 'foo';

  return {
    publicProperty: 'bar',
  }
})();

test.js

var test1 = function() {
  var privateProperty = 'foo';

  return {
    publicProperty: 'bar',
  }
};

index.html ...

<script src="./test.js"></script>
<script src="./test1.js"></script>

<script>
  console.log(test.privateProperty); // => undefined
  console.log(test1.privateProperty); // => undefined
</script>

当我尝试上述方法时,无论哪种情况,我都无法访问 privateProperty。人们在谈论的名字碰撞在哪里? IIFE 解决了什么问题?

【问题讨论】:

  • "privateProperty 无论如何都会被隐藏。" - 不是吗?它将与Module 在同一范围内,这正是我们在这里试图避免的?
  • 你说的是 es6 模块吗?
  • @Bergi 为什么我们要避免模块中的范围。我认为私有变量的意义在于它们隐藏在模块之外(或函数范围之外)。
  • @Bitten 是的。就像使用 module.exports 或 export 或 export default ...
  • @jro:在 (node, commonjs, ES6) 模块中,var privateProperty 确实已经是本地的了。 模块模式 的目的是在您必须使用在全局范围内执行的脚本而不是模块时允许同样的事情。

标签: javascript module iife


【解决方案1】:

privateProperty 无论如何都会被隐藏

不,如果没有 IIFE,privateProperty 将是全局范围的属性。

除非你在谈论一个模块加载器,它(在幕后)基本上与 IIFE 相同,它将整个文件包装在一个函数中,有点像:

var factory = Function("require, module, exports, global", yourFileBody );

然后用正确的值调用工厂;这也是你拥有这些机制的原因;因为它们是作为函数参数注入的。

这就是这些“私有”属性不会污染全局命名空间的方式。

编辑:

我尝试了一个没有任何 module.exports 的示例,但我仍然不明白 IIFE 正在解决什么问题。我在编辑 2 中发布了示例

test1 是一个工厂,而不是一个模块。让我们移除工厂并提取生成的模块,然后进行一些小的更改,以使这个私有状态有意义。 (将 publicProperty 变成一个函数“speak”并实际使用那里的私有属性/值/状态)

//the generted Module boils down to:
var test1 = { 
    name: "test1",
    speak() { 
        return this.name + " says " + private;
    } 
};
//and the private state also has to be kept somewhere:
var private = "Hello World";

现在让我们检查一下模块

console.log("test1", test1);
console.log("test1.speak()", test1.speak());
console.log("test1.private", test1.private);

很好,一切如预期
但是等等,这是什么?

console.log(
    "Look here:",
    private,
    this.private,
    window.private
)

哦不,有人暴露了我的私人财产!每个人都可以看到它。
如果其他一些脚本也定义了私有属性会发生什么?

var private = "Bye Folks";
var test1 = { 
    name: "test2",
    speak() { 
        return this.name + " says " + private;
    } 
};

console.log("test2", test2);
console.log("test2.speak():", test2.speak());

很好,很好。怎么样...

console.log("test1.speak():", test1.speak());

哦不,test1 坏了。它应该说“Hello World”......我希望有一种方法可以让我的 private 属性真正私有,这样其他人就不会乱用它。

https://jsfiddle.net/crkguf6b/

@jro,你现在明白了吗?这样的工厂封装了私有状态,这样它就不会污染全局命名空间,也不会被一些不同的代码弄乱;同时只公开一个公共 API。 您开始提出问题的 IIFE 实际上是一个匿名工厂,它会立即被调用以创建此对象/模块的一个实例,然后获取 GC。如顶部所示,模块加载器以相同的方式执行此操作。他们从您的 JS 文件中在幕后(或在预处理步骤中)创建这些工厂,并在必要时调用它们。

结论:

privateProperty 不是语言固有的,它是私有的。这是“人造的”。如果没有将 JS 文件包装在函数中的模块加载器,没有工厂,也没有 IIFE,privateProperty 就不是私有的。

也许这会随着 ES6 模块而改变。也许它会得到 JS 的一个内在部分,即每个文件都被视为一个单独的模块,因此文件中的普通 var foo; 不会最终出现在全局命名空间中,但目前不是。

【讨论】:

  • 如果我有上面的模块,除了没有 IIFE,我无法从全局范围访问 privateProperty。
  • 你是如何“加载”这个模块的?
  • module.exports 或导出
  • 确实,我们正在谈论一个模块加载器。关于模块加载器的工作原理,我写了什么?为什么不尝试将上面的模块放在&lt;script&gt; 标签中,然后检查结果,或者将其放入文件中,然后使用&lt;script src="yourFile.js"&gt;&lt;/script&gt;。也许您会有所不同,以及模块加载器为您做了什么样的工作。
  • 我尝试了一个没有任何 module.exports 的示例,但我仍然不明白 IIFE 正在解决什么问题。我在编辑 2 中发布了示例。
【解决方案2】:

您的test1 文件包含普通函数,不是模块模式,也不是无模块模式。试试这个:

<script>
var testA = (function() {
  var privateVariable = 'bar A';

  return {
    publicProperty: 'foo' + privateVariable
  }
})();
</script>
<script>
var privateVariable = 'bar B';
var testB = {
  publicProperty: 'foo' + privateVariable
};
</script>
<script>
console.log(testA.publicProperty); // => 'foobar A'
console.log(testB.publicProperty); // => 'foobar B'
console.log(privateVariable); // => 'bar B'
</script>

这不是关于任何东西的属性,而是关于脚本中的全局范围。

【讨论】:

    【解决方案3】:

    没有模块,你必须使用另一个变量。

    var Test = new test(someX);
    

    然后以Test.replace 访问属性。

    使用模块模式,您可以简单地使用

    Module.publicProperty
    

    加载后立即。

    【讨论】:

    • 我想我提出了一个分散问题的额外观点。所以我继续删除它。问题是 IIFE 如何隐藏私有变量。考虑到作用域在 JS 中的工作方式,这似乎是多余的。
    • 它们都使用函数作用域以相同的方式隐藏变量。它们之间的区别在于它们使用方式的其他方面,我的回答描述了其中一个区别。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-27
    • 1970-01-01
    相关资源
    最近更新 更多