我很难理解本书中的一些示例代码 JavaScriptAllongé (在线版免费)。
示例代码是用于计算给定直径的周长的函数。它显示了使用名称绑定值的不同方法。根据这本书,一种方法是:
(
(diameter) =>
((PI) => diameter * PI)(3.14159265)
)(2);
// calculates circumference given diameter 2
它进一步指出:
好吧,与此相关的皱纹通常是,调用函数比评估表达式要昂贵得多。每次调用外部函数时,我们都会调用内部函数。我们可以通过写作解决这个问题
(
((PI) =>
(diameter) => diameter * PI
)(3.14159265)
)(2);
我无法理解如何通过调用两个函数来解决这种情况,两个函数中是不是只有两个函数调用?
他们如何彼此不同?
这可能看起来有点令人困惑,因为我认为它没有得到很好的解释。或者,我认为它不是以典型的JavaScript方式解释的。
让我们分解一下这些例子
第一个例子
分解
var calculateCircumference = (diameter) => (
(PI) => diameter * PI)(3.14159265)
);
calculateCircumference(2); // 6.2831853
安排如下,如果您调用此代码,会发生以下情况
- 你通过直径(例如,2)
- 一个 新 创建的函数
PI
作为参数并使用它来计算周长。立即调用此函数
- 该函数使用两个变量来进行计算
除了浪费计算方式(两次调用)之外,这个例子也没有充分的理由进行复杂化。内在的功能是毫无意义的,并没有获得任何东西。这可能是这个例子失去了很多清晰度的地方 - 似乎是让这个例子按原样工作的唯一原因,就是设置第二个例子。
第二个例子
在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是 一点点 喜欢部分分配功能但是 两个不是(必然)相同的东西。
分解
话虽如此,让我们看看第二个例子:
//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
功能,你只是在评估 内 功能。
你应该看看 立即调用的函数表达式 (IIFE);那是一个 设计模式...
基本上:你声明一个函数并立即调用它...这有时被用作创建词法范围的权宜之计,只是为了避免全局变量......
// 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'); })();
如您所见,我们使用括号来包装/执行表达式 (...)
和括号作为 function call operator
。这意味着: 评估该表达式并调用它返回的内容。
当然,因为我们正在使用函数,所以我们可以传递它们的参数:
function log(what) { console.log(3, what); }
log('Foo');
// IIFE
(function(what) { console.log(4, what); })('Foo');
你可能已经知道的最后一件事是 Arrow Function
,介绍 ECMAScript中6:
(what => console.log(what))('Foo');
最后,你正在与IIFE的嵌套往返战斗。
我相信重点是“每次我们调用外部函数时......“,这确实令人困惑,因为外部函数仅在示例中被调用一次(作为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));
达到同样的效果:-)
本书可能暗示的是JavaScript编译器更有可能 一致 第二种方法中的PI函数。但是,如果我们用不同的动态直径多次调用这些方法,这才有意义。否则,编译器也可能同样内联直径函数。
在一天结束时,从性能角度来看真正重要的是JavaScript引擎是什么 真 无论如何都要做这些功能。
以下是一项测试,表明两种方法之间几乎没有差异。至少在我的盒子上。
您可能希望执行更多迭代,但请注意,这显然非常慢 边缘。
// 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;
}