JavaScript的函数式编程基础指南(2)

这个函数(applyFirst)接受一个二元函数作为其中一个参数,可以把第一个参数(即二元函数)看作是这个applyFirst函数的“部分操作”,然后返回一个一元(一个参数)函数,该一元函数被调用的时候返回外部函数的第一个参数(f)的二元函数f(a, b)。返回两个参数的二元函数。

让我们再谈谈一些函数,例如mult(乘法)函数:

var mult = function(a, b) { return a * b; };

依循mult(乘法)函数的逻辑,我们可以写一个新的函数double(乘方):

var double = applyFirst(mult, 2); double(32); // 64 double(7.5); // 15

这就是偏函数,在FP中经常会用到。(译注:FP全名为 Functional Programming 函数式程序设计 )

我们当然可以像applyFirst那样定义函数:

var curry2 = function (f) { return function (a) { return function (b) { return f(a, b); }; }; };

现在,我想要一个double(乘方)函数,我们换种方式做:

var double = curry2(mult)(2);

这种方式被称作“函数柯里化”。有点类似partial application(偏函数应用),但是更强大一点。

准确的说,函数式编程之所以强大,大部分因于此。简单和易理解的函数成为我们构筑软件的基础构件。当拥有高水平的组织能力、很少重用的逻辑的时候,函数能够被组合和混合在一起用来表达出更复杂的行为。

高阶函数可以得到的乐趣更多。让我们看两个例子:

1.翻转二元函数参数顺序

// flip the argument order of a function var flip = function (f) { return function (a, b) { return f(b, a); }; }; divide(10, 5) === flip(divide)(5, 10); // true

2.创建一个组合了其他函数的函数

// return a function that's the composition of two functions... // compose (f, g)(x) -> f(g(x)) var compose = function (f1, f2) { return function (x) { return f1(f2(x)); }; }; // abs(x) = Sqrt(x^2) var abs = compose(sqrt, square); abs(-2); // 2

这个例子创建了一个实用的函数,我们可以使用它来记录下每次函数调用。

var logWrapper = function (f) { return function (a) { console.log('calling "' + f.name + '" with argument "' + a); return f(a); }; }; var app_init = function(config) { /* ... */ }; if(DEBUG) { // log the init function if in debug mode app_init = logWrapper(app_init); } // logs to the console if in debug mode app_init({ /* ... */ });

词法闭包+作用域

我深信理解如何有效利用闭包和作用域是成为一个伟大JavaScript开发者的关键。
那么…什么是闭包?

简单的说,闭包就是内部函数一直拥有父函数作用域的访问权限,即使父函数已经返回。<译注4>
可能需要个例子。

var createCounter = function () { var count = 0; return function () { return ++count; }; }; var counter1 = createCounter(); counter1(); // 1 counter1(); // 2 var counter2 = createCounter(); counter2(); // 1 counter1(); // 3

一旦createCounter函数被调用,变量count就被分配一个新的内存区域。然后,返回一个函数,这个函数持有对变量count的引用,并且每次调用的时候执行count加1操作。

注意从createCounter函数的作用域之外,我们是没有办法直接操作count的值。Counter1和Counter2函数可以操作各自的count变量的副本,但是只有在这种非

常具体的方式操作count(自增1)才是被支持的。

在JavaScript,作用域的边界检查只在函数被声明的时候。逐个函数,并且仅仅逐个函数,拥有它们各自的作用域表。(注:在ECMAScript 6中不再是这样,因为let的引入)

一些进一步的例子来证明这论点:

// global scope var scope = "global"; var foo = function(){ // inner scope 1 var scope = "inner"; var myscope = function(){ // inner scope 2 return scope; }; return myscope; }; console.log(foo()()); // "inner" console.log(scope); // "global"

关于作用域还有一些重要的事情需要考虑。例如,我们需要创建一个函数,接受一个数字(0-9),返回该数字相应的英文名称。

简单点,有人会这样写:

// global scope... var names = ['zero','one','two','three','four','five','six','seven','eight','nine']; var digit_name1 = function(n){ return names[n]; };

但是缺点是,names定义在了全局作用域,可能会意外的被修改,这样可能致使digit_name1函数所返回的结果不正确。
那么,这样写:

var digit_name2 = function(n){ var names = ['zero','one','two','three','four','five','six','seven','eight','nine']; return names[n]; };

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wgyjsy.html