【问题标题】:Define a function within another function in JavaScript在 JavaScript 中的另一个函数中定义一个函数
【发布时间】:2012-04-18 06:58:23
【问题描述】:
function foo(a) {
    if (/* Some condition */) {
        // perform task 1
        // perform task 3
    }
    else {
        // perform task 2
        // perform task 3
    }
}

我有一个结构类似于上面的函数。我想将task 3抽象成一个函数bar(),但我希望将这个函数的访问限制在foo(a)的范围内。

为了达到我想要的,改成下面这样对吗?

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

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

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

【问题讨论】:

  • 自己测试是否值得:jsperf.com 我想这取决于 task3。
  • @tomByer - +1 推荐工具

标签: javascript


【解决方案1】:

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

  • bar 是在每次调用函数 foo 时创建的,但是:
    • 在现代浏览器上,这是一个非常快的过程。 (有些引擎很可能只为它编译一次代码,然后每次在不同的上下文中重用该代码;Google 的 V8 引擎 [在 Chrome 和其他地方] 在大多数情况下都会这样做。)李>
    • 根据bar 所做的事情,某些引擎可能会确定它们可以“内联”它,从而完全消除函数调用。 V8 做到了这一点,我敢肯定它不是唯一这样做的引擎。当然,只有在不改变代码行为的情况下,他们才能这样做。
  • 每次创建bar 对性能的影响(如果有的话)在 JavaScript 引擎之间会有很大差异。如果bar 是微不足道的,它会从无法检测到相当小。如果您没有连续调用foo 数千次(例如,来自mousemove 处理程序),我不会担心。即使您是,我也只会在看到速度较慢的引擎出现问题时才会担心。 Here's a test case involving DOM operations,这表明存在影响,但影响微不足道(可能被 DOM 的东西洗掉了)。 Here's a test case doing pure computation 显示出更高的影响,但坦率地说,我们说的是 秒的差异,因为即使是 92% 的增长也需要 秒发生仍然非常非常快。直到/除非您看到真实世界的影响,否则无需担心。
  • bar 只能从函数内部访问,并且它可以访问该函数调用的所有变量和参数。这使得这是一个非常方便的模式。
  • 请注意,由于您使用了函数声明,因此无论您将声明放在何处(顶部、底部或中间),只要它位于函数的顶层即可,而不是在流控制语句中,这是一个语法错误),它是在运行第一行逐步代码之前定义的。

【讨论】:

  • 感谢您的回答。所以你是说这对性能的影响可以忽略不计吗? (假设每次调用foo 时都会创建bar 的副本)
  • @ahmoo:对于 JavaScript 性能,答案几乎总是:视情况而定。 :-) 这取决于运行它的引擎以及您调用foo 的频率。如果您没有连续调用foo 数千次(例如,不在mousemove 处理程序中),那么我根本不会担心。请注意,某些引擎(例如 V8)无论如何都会内联代码,完全消除函数调用,前提是这样做不会以可以外部检测的方式改变正在发生的事情。
  • @T.J.Crowder:你能评论一下 robrich 的回答吗?该解决方案是否会阻止在每次通话中重新创建 bar()?另外,使用foo.prototype.bar 定义函数有帮助吗?
  • @rkw:创建一次函数,就像 robrich 的回答一样,是一种避免每次调用都创建它的成本的有用方法。你失去了bar 可以访问变量和参数以调用foo 的事实(任何你想要它操作的东西,你都必须传递它),这可能会使事情变得有点复杂,但在性能方面 -在遇到实际问题的危急情况下,您可能会像这样重构,看看它是否能解决问题。不,使用 foo.prototype 并没有真正的帮助(一方面,bar 将不再是私有的)。
  • @ahmoo:添加了一个测试用例。有趣的是,我从 Guffa 的测试用例中得到了不同的结果,我认为他的功能可能有点简单了。但我仍然认为性能不是问题。
【解决方案2】:

这就是闭包的用途。

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 的引用从匿名函数返回,该匿名函数只被调用一次以创建闭包。

无法通过这种方式从外部访问酒吧。

【讨论】:

  • 有趣。我对 javascript 的接触有限,所以闭包对我来说是新事物。但是,您已经为我研究结束标记了一个起点。谢谢。
  • 你多久使用一次闭包来处理变量/函数范围?例如,如果您有 2 个函数需要访问相同的 3 个变量,您会在闭包中声明 3 个变量以及 2 个函数,然后返回 2 个函数吗?
【解决方案3】:
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()的每一次执行都会用到它。

【讨论】:

  • 有趣。那我得查查封号了。谢谢。
  • 你多久使用一次闭包来处理变量/函数范围?例如,如果您有 2 个函数需要访问相同的 3 个变量,您是否会在闭包中声明 3 个变量以及 2 个函数,然后返回 2 个函数?
  • 我多久使用一次闭包?每时每刻。如果我有 2 个函数需要 3 个变量: 1. (最好)将 3 个变量传递给两个函数——函数可以定义一次且只能定义一次。 2.(好)创建两个函数,其中变量超出两者的范围。这是一个关闭。 (基本上是这里的答案。)可悲的是,每次新使用变量时都会重新定义函数。 3.(坏)不要使用函数,只用一个大的长方法来做这两件事。
  • 你说每次新使用的变量都会重新定义函数。你能解释一下吗? @robrich
  • @BudiSalah 当您执行foo 函数时,它会创建 bar 函数和它返回的匿名函数。再次调用它,您将创建两个新函数。第三次调用,函数又被新建了。
【解决方案4】:

是的,效果很好。

每次输入外部函数时,内部函数都不会重新创建,而是重新赋值。

如果你测试这段代码:

function test() {

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

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

}

test();
test();

它将显示1212,而不是1222

【讨论】:

  • 感谢您的回答。每次调用test() 时重新分配demo() 是否应该是性能问题?是否取决于demo()的复杂度?
  • 我做了一个性能测试:jsperf.com/inner-function-vs-global-function 结论是这通常不是性能问题(因为您放入函数中的任何代码都需要比创建函数本身更长的时间来运行) ,但如果您需要额外的性能优势,则必须为不同的浏览器编写不同的代码。
  • 感谢您花时间创建测试并分享您对性能的看法。非常感谢。
  • 您非常自信地说“内部函数不是每次都重新创建”。根据规范,它是;引擎是否优化取决于引擎。 (我希望大多数人都会。)我很高兴看到您的测试用例和我的测试用例有如此不同的结果:jsperf.com/cost-of-creating-inner-function 不过,我不认为性能是一个问题。
  • @T.J.Crowder:嗯,是的,这是一个实现细节,但是随着现代 Javascript 引擎编译代码,它不会在每次分配时重新编译函数。性能测试结果不同的原因是因为它们测试不同的东西。我的测试将全局函数与局部函数进行比较,而您的测试将局部函数与内联代码进行比较。内联代码当然会比调用函数更快,这是一种常见的优化技术。
【解决方案5】:

我创建了一个 jsperf 来测试嵌套与非嵌套以及函数表达式与函数声明,我惊讶地发现嵌套测试用例的执行速度比非嵌套快 20 倍。 (我预计会出现相反的差异或可以忽略不计的差异)。

https://jsperf.com/nested-functions-vs-not-nested-2/1

这是在 Chrome 76、macOS 上。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-11
    • 1970-01-01
    • 1970-01-01
    • 2012-01-15
    • 1970-01-01
    • 2018-07-04
    • 1970-01-01
    相关资源
    最近更新 更多