需要帮助理解JavaScript中的函数调用

时间:2016-08-07 11:52:39

标签: javascript

我很难理解书中 JavaScript Allongé (在线版免费)中的一些示例代码。

示例代码是用于计算给定直径的圆周的函数。它显示了使用名称绑定值的不同方法。根据这本书,一种方法是:

(
 (diameter) =>
  ((PI) => diameter * PI)(3.14159265)
)(2);
// calculates circumference given diameter 2

进一步指出:

  

嗯,这样的问题是,通常,调用函数比评估表达式要昂贵得多。每次调用外部函数时,我们都会调用内部函数。我们可以通过写

来解决这个问题
(
 ((PI) =>
   (diameter) => diameter * PI
 )(3.14159265)
)(2);

我无法理解如何通过调用两个函数来解决这种情况,两个函数中是不是只有两个函数调用? 他们如何彼此不同?

4 个答案:

答案 0 :(得分:6)

这可能看起来有点混乱,因为我不认为它解释得很好。或者说,我不认为它是以典型的JavaScript方式解释的。

让我们分解一下例子

第一个例子

击穿

var calculateCircumference = (diameter) => (
    (PI) => diameter * PI)(3.14159265)
);

calculateCircumference(2); // 6.2831853

如此安排,如果您调用此代码

,会发生以下情况
  1. 您通过直径(例如,2)
  2. 创建 new 函数,该函数将PI作为参数并使用它来计算周长。立即调用此函数
  3. 该函数使用两个存在的变量进行计算
  4. 除了浪费计算方式(两次调用)之外,这个例子也是没有充分理由的。内在的功能毫无意义,并没有给你带来任何好处。它可能是这个例子失去了很多清晰度的地方 - 似乎是让这个例子按原样工作的唯一理由,就是设置第二个例子。

    第二个例子

    关于currying

    在解决这个例子之前,似乎这本书可能没有提到它究竟是如何运作的。第二个示例利用了一种名为curry的技术,该技术用于函数式编程 - 它不是特定于JavaScript,但它仍然被广泛称为JavaScript世界中的名称。关于currying的简要概述

    //non-curried
    function add(a, b) { // or, in ES6: (a, b) => a + b;
        return a + b;
    }
    
    //curried
    function curryAdd(a) { //or in ES6: (a) => (b) => a + b;
        return function(b) {
            return a + b;
        }
    }
    
    //invocation
    add(2, 3); // 5
    curryAdd(2)(3); // 5
    

    我不会详细介绍,但实质上,一个带有多个参数的curried函数可以传递得更少,它将返回一个可以接受其余部分的新函数。当满足所有参数时,您将得到结果 - 以正式表示法,curryAdd函数将表示为curryAdd :: Number -> Number -> Number - 它是一个函数,它接受一个数字并返回另一个函数,取一个最终返回另一个数字的数字。为什么你会想要这样做,这里有一个例子 - 它是微不足道的,但它得到了重点:

    //add5:: Number -> Number
    add5 = curryAdd(5);
    
    add5(3); // 8
    add5(10); // 15
    [1, 2, 3].map(add5); // [6, 7, 8]
    

    Currying是有点,就像部分分配函数一样,只有two are not (necessarily) the same thing

    击穿

    话虽如此,让我们看看第二个例子:

    //curryMultiply :: Float -> Float -> Float
    (PI) => (diameter) => diameter * PI
    //another way to write it:
    //(a) => (b) => a * b
    

    希望能澄清一下发生了什么。我会将示例的其余部分重新编写为实际发生的事情:

    // calculateCircumference :: Float -> Float
    var calculateCircumference = curryMultiply(3.14159265);
    
    calculateCircumference(2); //6.2831853
    

    第二个例子的代码与上面的代码相同。它避免了两次调用函数,因为外部函数(我称之为curryMultiply)只被调用一次 - 每次调用calculateCircumference函数时,你只是在评估< em>内部功能。

答案 1 :(得分:3)

你应该看看immediately-invoked function expression(IIFE);这是design pattern ...

基本上:你声明一个函数并立即调用它...这有时被用作创建词法范围的权宜之计,只是为了避免全局变量......

&#13;
&#13;
// The way we're confident...
function logFoo() { console.log(1, 'FOO'); }
logFoo();

// Using and IIFE
(function() { console.log(2, 'FOO'); }());
// OR for better readability
(function() { console.log(2, 'FOO'); })();
&#13;
&#13;
&#13;

如您所见,我们使用括号来包装/执行表达式(...),将括号用作function call operator。这意味着:评估该表达式并调用它返回的内容

当然,因为我们正在使用函数,所以我们可以传递它们的参数:

&#13;
&#13;
function log(what) { console.log(3, what); }
log('Foo');

// IIFE
(function(what) { console.log(4, what); })('Foo');
&#13;
&#13;
&#13;

您可能已经知道的最后一件事是由Arrow Function引入的ECMAScript 6

&#13;
&#13;
(what => console.log(what))('Foo');
&#13;
&#13;
&#13;

最后,您正在与IIFE的嵌套往返进行战斗。

答案 2 :(得分:3)

我认为强调的是短语“每次我们调用外部函数...... ”,这确实令人困惑,因为外部函数仅在示例中被调用一次(作为IEFE)。通过这个例子,人们应该能够更好地掌握差异:

const circumference = (diameter) => 
  ((PI) =>
    diameter * PI
  )(3.14159265);
console.log(circumference(2));
console.log(circumference(5));

const circumference = ((PI) =>
  (diameter) =>
    diameter * PI
)(3.14159265);
console.log(circumference(2));
console.log(circumference(5));

但显然作者不想在这里引入变量声明,所以也许它会被写成

((circumference) => {
  console.log(circumference(2));
  console.log(circumference(5));
})(((PI) =>
  (diameter) =>
    diameter * PI
)(3.14159265));

达到同样的效果: - )

答案 3 :(得分:2)

本书可能暗示的是,JavaScript编译器更有可能inline第二种方法中的PI函数。但是,如果我们用不同的动态直径多次调用这些方法,这才有意义。否则,编译器也可能同样内联直径函数。

在一天结束时,从性能角度看真正重要的是JavaScript引擎真正无论如何都要使用这些功能。

以下测试表明两种方法之间几乎没有差异。至少在我的盒子上。

您可能希望执行更多迭代,但请注意Edge上这显然非常慢。

// This is a warmup to make sure that both methods are passed through
// Just In Time (JIT) compilation, for browsers doing it that way.
test1(1E5);
test2(1E5);

// Perform actual test
console.log('Method #1: ' + test1(1E6).toFixed(2) + 'ms');
console.log('Method #2: ' + test2(1E6).toFixed(2) + 'ms');

function test1(iter) {
  var res, n, ts = performance.now();

  for(n = 0; n < iter; n++) {
    res = (
      (diameter) => ((PI) => diameter * PI)(3.14159265)
    )(Math.random() * 10);
  }
  return performance.now() - ts;
}

function test2(iter) {
  var res, n, ts = performance.now();

  for(n = 0; n < iter; n++) {
    res = (
      ((PI) => (diameter) => diameter * PI)(3.14159265)
    )(Math.random() * 10);
  }
  return performance.now() - ts;
}