在JavaScript中的另一个函数中定义一个函数


82
function foo(a) {
    if (/* Some condition */) {
        // perform task 1
        // perform task 3
    }
    else {
        // perform task 2
        // perform task 3
    }
}

我有一个函数,其结构与上述类似。我想将任务3抽象为一个函数,bar()但是我希望将此函数的访问限制在的范围内foo(a)

为了实现我想要的,更改以下内容是否正确?

function foo(a) {
    function bar() {
        // Perform task 3
    }

    if (/* Some condition */) {
        // Perform task 1
        bar();
    }
    else {
        // Perform task 2
        bar();
    }
}

如果上述正确,那么bar()每次foo(a)调用时都会重新定义吗?(我担心这里会浪费CPU资源。)


1
测试自己是否值得:jsperf.com我想这取决于task3。
tomByrer 2012年

1
@tomByer-+1暗示工具
tamakisquare 2012年

Answers:


121

是的,您所拥有的是正确的。一些注意事项:

  • bar是在每次调用函数时创建的foo,但是:
    • 在现代浏览器中,这是一个非常快速的过程。(某些引擎可能只为它编译一次代码,然后每次在不同的上下文中重复使用该代码;在大多数情况下,Google的V8引擎(在Chrome和其他地方)都可以做到这一点。)
    • 并且根据具体bar操作,某些引擎可能会确定可以“内联”它,从而完全消除了函数调用。V8可以做到这一点,我敢肯定它不是唯一的引擎。自然,他们只有在不改变代码行为的情况下才能这样做。
  • bar每次创建时对性能的影响(如果有的话)在JavaScript引擎之间差异很大。如果bar比较琐碎,它将从无法检测到相当小的变化。如果您没有foo连续调用数千次(例如,从mousemove处理程序中调用),则无需担心。即使您是我,也只会在看到较慢的发动机出现问题时才担心它。这是一个涉及DOM操作的测试用例,表明有影响,但是影响很小(可能被DOM东西冲走了)。这是一个做纯计算的测试用例,它显示出更高的影响,但坦率地说,我们所说的是微秒的差异,因为即使是耗的东西,即使增加92%秒的发生仍然非常非常快。直到/除非您看到了现实世界的影响,否则您不必担心。
  • bar只能从函数内部访问,并且可以访问该函数调用的所有变量和参数。这使它成为非常方便的模式。
  • 请注意,因为您使用了函数声明,所以放置声明(顶部,底部或中间位置)的位置无关紧要,只要它位于函数的顶层而不是流控制语句中即可,语法错误),则必须在运行逐步代码的第一行之前定义该错误。

谢谢你的回答。那么,您是说这对性能的影响微不足道吗?(假设bar在的每个调用中都创建了的副本foo
tamakisquare

2
@ahmoo:凭借JavaScript性能,答案几乎总是:取决于情况。:-)取决于将在哪个引擎上运行它以及调用的频率foo。如果您没有foo连续调用数千次(例如,没有在mousemove处理程序中),那么我完全不必担心。并请注意,某些引擎(例如V8)无论如何都将内联代码,从而完全消除了函数调用,前提是这样做不会改变可从外部检测的方式。
TJ Crowder

@TJCrowder:你能评论罗伯里奇的答案吗?该解决方案是否会阻止bar()每次通话的重播?此外,使用foo.prototype.bar定义功能会有所帮助吗?
rkw 2012年

4
@rkw:一次创建函数,就像robrich的答案一样,是避免在每次调用时创建函数的成本的有用方法。您失去了可以bar访问变量和参数以进行调用的事实foo(您要对其进行操作的任何事物,都必须传递它),这可能会使事情复杂化,但是在性能至关重要的情况下,您已经看到一个实际的问题,您可以像这样重构以查看它是否可以解决问题。不,使用foo.prototype不会真正有帮助(一方面,它bar不再是私有的)。
TJ Crowder 2012年

@ahmoo:添加了一个测试用例。有趣的是,我从Guffa的测试案例中得到了不同的结果,我认为他的功能可能有点简单了。但是我仍然认为性能不是问题。
TJ Crowder 2012年

15

这就是闭包的用途。

var foo = (function () {
  function bar() {
    // perform task 3
  };

  function innerfoo (a) { 
    if (/* some cond */ ) {
      // perform task 1
      bar();
    }
    else {
      // perform task 2
      bar();
    }
  }
  return innerfoo;
})();

Innerfoo(一个闭包)保存对bar的引用,而匿名函数仅返回对innerfoo的引用,该匿名函数仅被调用一次即可创建该闭包。

这样无法从外部访问酒吧。


1
有趣。我对javascript的接触有限,因此关闭对我来说是新事物。但是,您已为我学习封闭学习打下了起点。谢谢。
tamakisquare

您多久使用一次闭包来处理变量/函数作用域?例如,如果您有2个函数需要访问相同的3个变量,您是否会在闭包中声明3个变量以及2个函数,然后返回2个函数?
doubleOrt

8
var foo = (function () {
    var bar = function () {
        // perform task 3
    }
    return function (a) {

        if (/*some condition*/) {
            // perform task 1
            bar();
        }
        else {
            // perform task 2
            bar();
        }
    };
}());

闭包保留bar()包含的范围,从自执行的匿名函数返回新函数,将更可见的范围设置为foo()。匿名自执行函数仅运行一次,因此只有一个bar()实例,每次执行foo()都会使用它。


有趣。那我要查封。谢谢。
tamakisquare

您多久使用一次闭包来处理变量/函数作用域?例如,如果您有2个函数需要访问相同的3个变量,您是否会在闭包中声明3个变量以及2个函数,然后返回2个函数?
doubleOrt

我多久使用一次关闭?每时每刻。如果我有2个函数需要3个变量:1.(最好)将3个变量传递给两个函数-这些函数只能定义一次,而只能定义一次。2.(好的)创建两个变量不在两个函数范围内的函数。这是一个关闭。(这里基本上是答案。)可悲的是,每次使用新变量时都会重新定义函数。3.(不好的)不使用函数,只有一种大的长方法同时做这两种事情。
robrich

5

是的,那很好。

每次您输入外部函数时都不会重新创建内部函数,但是会重新分配它。

如果您测试此代码:

function test() {

    function demo() { alert('1'); }

    demo();
    demo = function() { alert('2'); };
    demo();

}

test();
test();

它会显示1212,不1222


感谢您的回答。是否应该将demo()每次重新分配test()称为绩效问题?是否取决于的复杂性demo()
tamakisquare

1
我进行了性能测试:jsperf.com/inner-function-vs-global-function结论是,通常这不是性能问题(因为您放入函数中的任何代码都比创建函数花更长的时间才能运行)本身),但是如果您需要额外的性能优势,则必须为不同的浏览器编写不同的代码。
Guffa

感谢您花时间来创建测试,以及分享性能方面的知识。非常感激。
tamakisquare

您已经充满信心地说“内部函数不会每次都重新创建”。根据规范,它是;引擎是否优化取决于引擎。(我希望大多数人都会这样做。)我很高兴看到您的测试用例和我的测试用例具有如此不同的结果:jsperf.com/cost-of-creating-inner-function不是我认为性能是一个问题。
TJ Crowder 2012年

@TJCrowder:是的,这是一个实现细节,但是随着现代Javascript引擎编译代码,它不会在每次分配函数时都重新编译该函数。性能测试的结果之所以不同,是因为它们测试的是不同的东西。我的测试将全局函数与本地函数进行比较,而您的测试将本地函数与内联代码进行比较。内联代码当然比调用函数更快,这是一种常见的优化技术。
Guffa

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.