此JavaScript模式称为什么,为什么使用它?


100

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

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

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

(实施例见光线投射方法这里)。

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

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

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

将第一个版本与正常版本进行比较,第一个版本的不同之处在于:

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

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

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

我的问题:

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

1
@ChrisHayes足够公平。我将其标记为THREE.js,因为我认为THREE.js贡献者最有资格回答此问题,但是是的,这是一个通用的JS问题。
帕特里克·克鲁格2014年

2
我相信这叫做闭包。您可以阅读有关它们的信息。
StackFlowed 2014年

1
如果这是实例化Bar的唯一位置,则它是单例模式。
保罗

8
不一定要节省内存,但是可以在调用之间保持状态
Juan Mendes

2
@wrongAnswer:不完全是。在这里,匿名函数(将是闭包)被立即执行。
njzk2 2014年

Answers:


100

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

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

这称为立即调用函数表达式IIFE

  1. 它在此函数内定义了局部变量

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

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

同样,要点是该局部变量是private

此模式有名称吗?

可以将这种模式称为模块模式。报价单:

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

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

  1. 它正在实现Singleton设计模式。
  2. 使用第一个示例,可以控制创建特定类型的对象的方式。与这一点最接近的匹配可以是静态工厂方法有效Java中描述的。
  3. 如果每次都需要相同的对象状态,这将非常有效

但是,如果您每次只需要香草对象,那么此模式可能不会添加任何值。


1
+1可正确识别Addy Osmani的书中的图案。您的命名是正确的-这的确是模块模式-顺便说一下,这就是显示模块模式。
本杰明·格林巴姆2014年

4
除了“没有现成的私有变量”部分,我同意您的回答。所有JS变量都是按词法定义的“开箱即用”范围,比“私有”变量(例如,在Java中发现)具有更强大/更通用的机制;因此,JS支持“私有”变量作为处理所有变量的特殊方式。
Warbo 2014年

我认为“私有变量”是指私有“对象字段”
Ian Ringrose


4
@LukaHorvat:事实上,JavaScript没有比其他语言更“强大”(我更喜欢表达性)。实际上,它的表达性较差,因为保护变量的唯一方法是将其包含在函数中,以避免任何变量重用的废话。Module模式是制作好的javascript代码的绝对必要条件,但这不是该语言的功能,它是避免被该语言的弱点所困扰的一种可悲的解决方法。
Falanwe 2014年

11

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

尽管有可能确实限制了内存的使用,但是通常GC仍会收集未使用的对象,因此这种模式不太有帮助。

这种模式是关闭的一种特定形式。


1
在JS中,通常将其称为“模块”
Lesha Ogonkov

2
就其本身而言,我不会将其称为“特定形式的关闭”。这是使用闭包的模式。该模式的名称仍然值得关注。
克里斯·海斯

4
它真的需要名称吗?一切都必须成为一种模式吗?我们是否真的需要无穷无尽的“模块模式”变体分类法?难道就不是“带有一些返回函数的局部变量的IIFE”吗?
Dagg Nabbit 2014年

3
@DaggNabbit当问题是“此模式称为什么”时?是的,它需要一个名称,否则需要一个令人信服的论点,即没有名字。此外,存在模式是有原因的。我不明白您为什么在这里反对他们。
克里斯·海斯

4
@ChrisHayes如果需要一个名称,为什么要提出一个没有名称的参数呢?那没有任何意义。如果需要一个,则不能有一个。我对模式没有问题,但是我认为没有必要将每个简单的成语归为模式。这样做会导致思维方式有限(“这是模块模式吗?我是否正确使用了模块模式?”与“我有一个IIFE带有一些返回函数的局部变量,这种设计对我有用吗?”)
Dagg Nabbit 2014年

8

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

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

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


5

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

这对于保持调用之间的某些状态非常有用,类似于在类似staticC的语言中如何使用变量。

tmpObject 是仅对内部函数可见的私有变量。

它更改了内存使用量,但其目的不是为了节省内存。


5

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

在此处输入图片说明

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


0

在提供的示例中,第一个代码段将对foo()函数的每次调用使用相同的tmpObject实例,其中,与第二个代码段一样,tmpObject每次都将是一个新实例。

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

第一个片段的未立即执行的功能版本实际上如下所示:

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


3
您的“更好”的示例仅在NodeJS中有效,并且您没有解释它的更好之处。
本杰明·格林巴姆2014年

制作一个单独的模块并不是“更好”,只是有所不同。特别是,这是一种将高阶函数折叠为一阶对象的方法。一阶代码往往更易于遍历,但通常更为冗长,迫使我们对中间结果进行验证。
Warbo 2014年

@BenjaminGruenbaum模块不仅位于Node中,还有许多客户端模块解决方案,例如browserify。我认为模块解决方案“更好”,因为它更具可读性,更易于调试,并且在范围和位置方面更明确。
Kory Nunn
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.