【问题标题】:What is this JavaScript pattern called and why is it used?这个 JavaScript 模式叫什么,为什么要使用它?
【发布时间】:2014-11-23 09:15:23
【问题描述】:

我正在研究 THREE.js 并注意到函数定义如下的模式:

var foo = ( function () {
    var bar = new Bar();

    return function ( ) {
        //actual logic using bar from above.
        //return result;
    };
}());

(示例见光线投射方法here)。

这种方法的正常变体如下所示:

var foo = function () {
    var bar = new Bar();

    //actual logic.
    //return result;
};

将第一个版本与正常变体进行比较,第一个版本似乎不同:

  1. 它分配自执行函数的结果。
  2. 它在这个函数中定义了一个局部变量。
  3. 它返回实际函数,其中包含使用局部变量的逻辑。

因此主要区别在于,在第一个变体中,bar 在初始化时只分配一次,而第二个变体在每次调用时都会创建这个临时变量。

我对为什么使用它的最佳猜测是它限制了 bar 的实例数量(只有一个),从而节省了内存管理开销。

我的问题:

  1. 这个假设正确吗?
  2. 这种模式有名称吗?
  3. 为什么要使用这个?

【问题讨论】:

  • @ChrisHayes 很公平。我将其标记为 THREE.js,因为我认为 THREE.js 贡献者最有资格回答这个问题,但是是的,这是一个通用的 JS 问题。
  • 我相信它被称为闭包。你可以阅读它们。
  • 如果这是唯一实例化 Bar 的地方,那么它就是 singleton 模式。
  • 不一定是为了节省内存,但可以跨调用保持状态
  • @wrongAnswer:不完全是。这里匿名函数(将是闭包)立即执行。

标签: javascript closures iife


【解决方案1】:

您的假设几乎是正确的。让我们先回顾一下。

  1. 它分配一个自执行函数的返回

这称为Immediately-invoked function expressionIIFE

  1. 它在这个函数中定义了一个局部变量

这是在 JavaScript 中拥有私有对象字段的方式,因为它不提供 private 关键字或其他功能。

  1. 它返回包含使用局部变量的逻辑的实际函数。

再次强调,这个局部变量是私有的

这种模式有名字吗?

AFAIK 您可以将此模式称为Module Pattern。引用:

模块模式使用闭包封装“隐私”、状态和组织。它提供了一种包装公共和私有方法和变量的混合方式,防止碎片泄漏到全局范围内并意外与另一个开发人员的接口发生冲突。使用这种模式,只返回一个公共 API,将闭包中的所有其他内容保持为私有。

比较这两个示例,我对为什么使用第一个示例的最佳猜测是:

  1. 它正在实现单例设计模式。
  2. 可以使用第一个示例控制创建特定类型对象的方式。如 Effective Java 中所述,与这一点最接近的匹配可能是 static factory methods
  3. 如果您每次都需要相同的对象状态,那就是efficient

但如果你每次都只需要 vanilla 对象,那么这种模式可能不会增加任何价值。

【讨论】:

  • +1 用于正确识别 Addy Osmani 书中的模式。你的命名是正确的 - 这确实是模块模式 - 顺便说一下,揭示模块模式。
  • 我同意你的回答,除了“没有开箱即用的私有变量”部分。所有 JS 变量都是词法范围的“开箱即用”,这是一种比“私有”变量更强大/更通用的机制(例如在 Java 中发现的);因此 JS 支持“私有”变量作为它处理所有变量的一种特殊情况。
  • 我认为“私有变量”是指私有“对象字段”
  • @LukaHorvat :事实上,javascript 并不比其他语言更“强大”(我更喜欢表达性这个词)。实际上,它的表现力较差,因为保护变量的唯一方法是将它们包含在函数中以避免任何变量重用废话。模块模式是编写好的 javascript 代码的明确必要条件,但它不是语言的特性,它更像是一种可悲的解决方法,以避免被语言的弱点咬伤。
【解决方案2】:

它限制了对象初始化成本,并额外确保所有函数调用都使用相同对象。例如,这允许将状态存储在对象中以供将来调用使用。

虽然它可能会限制内存使用,但通常 GC 无论如何都会收集未使用的对象,因此这种模式不太可能有太大帮助。

此模式是closure 的特定形式。

【讨论】:

  • 在 JS 中它通常被称为“模块”
  • 就其本身而言,我不会称其为“特定形式的闭包”。这是一种使用闭包的模​​式。该模式的名称仍有待商榷。
  • 真的需要名字吗?一切都必须是模式吗?我们真的需要对“模块模式”变体进行无穷无尽的分类吗?它不能只是“一个带有一些返回函数的局部变量的 IIFE 吗?”
  • @DaggNabbit 当问题是“这种模式叫什么”时?是的,它需要一个名字,或者一个令人信服的论据,它没有名字。此外,模式的存在是有原因的。我不明白你为什么在这里指责他们。
  • @ChrisHayes 如果它需要一个名字,为什么你需要一个它没有名字的论点?这没有任何意义。如果它需要一个,它一定没有一个。我对模式没有任何问题,但我认为没有必要将每个简单的成语都归类为模式。这样做会导致以有限的方式思考(“这是模块模式吗?我是否正确使用模块模式?”与“我有一个带有一些返回函数的局部变量的 IIFE,这种设计对我有用吗?”)
【解决方案3】:

我不确定这个模式是否有更正确的名称,但这对我来说就像一个模块,使用它的原因是封装和维护状态。

闭包(由函数中的函数标识)确保内部函数可以访问外部函数中的变量。

在您给出的示例中,通过执行外部函数返回内部函数(并分配给foo),这意味着tmpObject 继续存在于闭包中,并且对内部函数foo() 的多次调用将运行在tmpObject 的同一个实例上。

【讨论】:

    【解决方案4】:

    您的代码与 Three.js 代码之间的主要区别在于,在 Three.js 代码中,变量 tmpObject 仅初始化一次,然后在每次调用返回的函数时共享。

    这对于在调用之间保持某些状态很有用,类似于在类 C 语言中使用 static 变量的方式。

    tmpObject 是一个私有变量,只对内部函数可见。

    它改变了内存使用,但它不是为了节省内存而设计的。

    【讨论】:

      【解决方案5】:

      我想通过扩展显示模块模式的概念来为这个有趣的线程做出贡献,该模式确保所有方法和变量在显式公开之前都保持私有。

      在后一种情况下,添加方法将被称为 Calculator.add();

      【讨论】:

        【解决方案6】:

        在提供的示例中,第一个 sn-p 将对函数 foo() 的每次调用使用相同的 tmpObject 实例,而在第二个 sn-p 中,tmpObject 每次都是一个新实例。

        可能使用第一个 sn-p 的一个原因是变量 tmpObject 可以在对 foo() 的调用之间共享,而不会将其值泄漏到声明 foo() 的范围内。

        第一个 sn-p 的非立即执行函数版本实际上如下所示:

        var tmpObject = new Bar();
        
        function foo(){
            // Use tmpObject.
        }
        

        但请注意,此版本的 tmpObject 与 foo() 在同一范围内,因此可以稍后对其进行操作。

        实现相同功能的更好方法是使用单独的模块:

        模块'foo.js':

        var tmpObject = new Bar();
        
        module.exports = function foo(){
            // Use tmpObject.
        };
        

        模块 2:

        var foo = require('./foo');
        

        IEF 和命名的 foo 创建函数的性能比较:http://jsperf.com/ief-vs-named-function

        【讨论】:

        • 您的“更好”示例仅适用于 NodeJS,您还没有解释它如何更好。
        • 制作一个单独的模块并不是“更好”,它只是不同。特别是,它是一种将高阶函数折叠为一阶对象的方法。一阶代码往往更容易单步执行,但通常更冗长,并迫使我们具体化中间结果。
        • @BenjaminGruenbaum 模块不仅在 Node 中,还有许多客户端模块解决方案,例如 browserify。我认为模块解决方案“更好”,因为它更具可读性,更易于调试,并且更明确地说明了范围内的内容和位置。
        猜你喜欢
        • 2011-02-20
        • 2018-06-29
        • 1970-01-01
        • 1970-01-01
        • 2011-01-23
        相关资源
        最近更新 更多