• JavaScript 函数式编程


     

    匿名函数

    似乎函数式编程变得越来越流行了. 甚至 Java 8 中也拥有了匿名函数(注: 应该是 lambda 表达式), C# 中也已经引入了很长一段时间了. 匿名函数是一种函数定义不会绑定到特定标识符的函数. 如果你不仅仅是编写一些简单的练习而使用 JavaScript, 我相信你应该对于匿名函数是很熟悉的了. 当使用 jQuery 时, 下面代码很可能是你首先会写到的:

    $(document).ready(function () {
        //do some stuff
    });

    传递给 $(document).ready 的函数就是一个实实在在的匿名函数. 这个理论提供了巨大的好处, 尤其在我们想保持事物的 DRY ( Don't repeat yourself ) 原则时.

    高阶函数

    高阶函数是可以接收函数作为参数或者将函数作为返回值的函数. 在 C#, Java 8, Python, Perl, Ruby 等语言中都可以返回并将函数作为参数... 作为最为流行的语言 - JavaScript 已经在很久之前就内置了这些函数式的要素. 下面是一个基本的例子:

    function animate(property, duration, endCallback) {
        //Animation here...
        if (typeof endCallback === 'function') {
            endCallback.apply(null);
        }
    }
    
    animate('background-color', 5000, function () {
        console.log('Animation finished');
    });

    上面的代码中存在一个名为 animate 的函数. 它的参数包含: 一个动画元素的属性, 持续时间和一个在动画完成时触发的回调函数. jQuery 中也有这种模式. jQuery 中含有大量的接收函数作为参数的方法, 例如 $.get:

    $.get('http://example.com/test.json', function (data) {
        //processing of the data
    });

    JavaScript 内的回调函数处处可见, 它们是完美的异步编程, 例如: 事件处理, ajax 请求, 客户端处理 ( Node.js ) 等等. 正如我上面提到过的, 通过使用回调函数可以避免重复工作. 当你需要基于条件而表现出不同行为的代码时, 回调函数真的是大有裨益的.

    还存在另一种高阶函数 - 返回值是函数的函数. JavaScript 同样存在大量非常有效的用例. 比如使用缓存:

    /* From Asen Bozhilov's lz library */
    lz.memo = function (fn) {
        var cache = {};
        return function () {
            var key = [].join.call(arguments, '§') + '§';
            if (key in cache) {
                return cache[key];
            }
            return cache[key] = fn.apply(this, arguments);
        };
    };

    代码中在父函数的局部作用域内定义了 cache 变量. 在每次调用时, 会首先检测是否函数已经被同样的参数调用过, 如果调用过则立即返回, 否则结果会被缓存并返回. 这里甚至有一种暗示: 如果使用相同的参数调用, 则函数会返回同样的结果. 这是一个典型的函数式编程思想.

    设想一下这样的情况:

    var foo = 1;
    function bar(baz) {
        return baz + foo;
    }
    var cached = lz.memo(bar);
    cached(1); //2
    foo += 1;
    cached(1); //2

    有一个名为 bar 的函数, 只接受一个参数 - baz, 返回值为 baz 和全局变量 foo 的和. 当使用 memo 将 bar 函数包装为可缓存的函数并保存一份拷贝的引用给 cached. 首先用参数 1 调用 cached, 函数返回 2. 之后 foo 自增 1 并再次调用 cached. 我们会得到相同的结果, 但却是错误的结果. 那是因为有某种状态存在, 这种状态会在之后的 Monads 部分中被考虑到. 因此我们还是继续把注意力放在高阶函数上, 尤其是返回函数的那些.

    闭包

    让我们再次看一下 memo. 函数内定义了局部变量 cache 并且返回可缓存的函数. 变量在返回的函数内也是可被访问的, 因为此处创建了一个闭包. 这就是函数式编程的又一个要素, 并且传播的十分广泛. JavaScript 内可以通过闭包实现私有权限:

    var timeline = (function () {
        var articles = [];
    
        function sortArticles() {
            articles.sort(function (a, b) {
                return a.name - b.name;
            });
        }
    
        return {
            getArticles: function () {
                return articles;
            },
            setArticles: function (articleList) {
               articles = articleList;
               sortArticles();
            }
        };
    }());

    这个例子里有一个名为 timeline 的对象. 它是一个立即执行函数表达式 ( IIFE ) 的执行结果, 返回了一个实为 timeline 的公共接口的对象, 对象中包含属性 getArticles 和 setArticles. 在 IIFE 的词法作用域的内部, 包含了 articles 数组和 sort 函数的定义, 它们通过 timeline 对象是无法直接调用的.

    这个主题将会以 ECMAScript 5 的高阶函数作为结束. 近年来, JavaScript 包含了越来越的的函数式要素. 当我们听到函数式编程, 首先浮现在我们脑中的可能就是 map 函数了. 它接收一个匿名函数和一个列表作为参数. 并将列表中的所有元素应用到这个函数中. map 如今已经正式成为 ECMAScript 5 的一部分了.

    下面是它的基本用法:

    [1,2,3,4].map(function (a) {
        return a * 2;
    });
    //[2,4,6,8]

    上面的代码中 map 将列表内的元素变为之前的 2 倍. map 并不是唯一被加入到 ECMAScript 5 的典型函数式编程语言的函数, 同样还有 filter 和 reduce.

    递归

    另一个广泛适用于几乎所有现代编程语言的元素则是递归. 它是一个在函数提内部调用函数本身的一种函数:

    function factorial(n) {
        if (n <= 1) return 1;
        return n * factorial(n - 1);
    }

    上面是一个使用递归来实现阶乘的例子. 这个概念应用的十分普遍, 因此就不再赘述了.

    状态管理(Monads)

    在 memoization 的例子中, JavaScript 不是一个纯粹的函数式语言 (也许这也是这门语言之所以如此流行的一个原因...) , 因为它包含了可变的数据与状态. 通常纯函数式编程语言 (比如: Haskell ) 使用 monads 管理状态. JavaScript 中有多种 Monads 的实现方式. 例如下面这个就是 Douglas Crockford 的实现:

    /* Code by Douglas Crockford */
    function MONAD(modifier) {
        'use strict';
        var prototype = Object.create(null);
        prototype.is_monad = true;
        function unit(value) {
            var monad = Object.create(prototype);
            monad.bind = function (func, args) {
                return func.apply(
                    undefined,
                    [value].concat(Array.prototype.slice.apply(args || []))
                );
            };
            if (typeof modifier === 'function') {
                modifier(monad, value);
            }
            return monad;
        }
        unit.method = function (name, func) {
            prototype[name] = func;
            return unit;
        };
        unit.lift_value = function (name, func) {
            prototype[name] = function () {
                return this.bind(func, arguments);
            };
            return unit;
        };
        unit.lift = function (name, func) {
            prototype[name] = function () {
                var result = this.bind(func, arguments);
                return result && result.is_monad === true ? result : unit(result);
            };
            return unit;
        };
        return unit;
    }

    这是一个简短的演示, 来说明如何创建一个一元 I/O:

    var monad = MONAD();
    monad(prompt("Enter your name:")).bind(function (name) {
        alert('Hello ' + name + '!');
    });

    在 JavaScript 内使用 monads 看起更像学术练习而并没有什么实际作用, 但是如果想在纯函数式方式下工作, 我们就能用到它了.

    Schönfinkelization (柯里化)

    Schönfinkelization 是一种函数式的变形, 它使我们可以逐步的填充函数的参数. 当函数接收到最后一个参数时才会返回结果. 它由 Moses Schönfinkel 发明并在之后由 Haskell Curry 重新发现. 下面是一个在 JavaScript 内的样例, 由 Stoyan Stefanov 实现:

    /* By Stoyan Stafanov */
    function schonfinkelize(fn) {
        var slice = Array.prototype.slice,
            stored_args = slice.call(arguments, 1);
        return function () {
            var new_args = slice.call(arguments),
                args = stored_args.concat(new_args);
            return fn.apply(null, args);
        };
    }

    接下来是一个使用函数来解二次方程的例子:

    function quadraticEquation(a, b, c) {
        var d = b * b - 4 * a * c,
            x1, x2;
        if (d < 0) throw "No roots in R";
        x1 = (-b - Math.sqrt(d)) / (2 * a);
        x2 = (-b + Math.sqrt(d)) / (2 * a);
        return {
            x1: x1,
            x2: x2
        }
    }

    如果需要逐步填充函数的参数则可以:

    var temp = schonfinkelize(quadraticEquation, 1);
    temp = schonfinkelize(temp, -2);
    temp(1); // { x1: 1, x2: 1 }

    如果想使用语言内置功能来取代 schonfinkelize 函数, 你可以使用 Function.prototype.bind . 由于它定义在所有函数的构造函数的原型属性上, 因此可以作为函数的方法进行使用. bind 会创建一个包含特定上下文与参数的全新函数. 例如:

    var f = function (a, b, c) {
      console.log(this, arguments);
    };

    接下来, 使用 bind 函数:

    var newF = f.bind(this, 1, 2);
    newF(); //window, [1, 2]
    newF = newF.bind(this, 3)
    newF(); //window, [1,2,3]
    newF(4); //window, [1,2,3,4]

    使用 bind 时候需要注意, 它并没有被广泛支持. 基于 kagax ES5 compatibility table , IE9 以上才支持 bind.

    这个功能在某些场景下是很实用的. 设想有一个 session 对象. session 的键可以同构复杂耗时的算法生成, 一旦用户访问网站就需要生成. session 对象也应该保存关于用户的一些信息, 例如: 客户选择的当前页面的皮肤. 这样就可以使用柯里化来调用 session 的生成函数, 一旦页面加载, 就会生成 session 的键. 在那之后可以再次调用函数来获取用户皮肤. 这样就会在用户选择完皮肤后才生成 session 对象, 但却不会有额外开销, 因为生成 session 键值的复杂算法已经在之前执行过了(这个过程对用于不敏感).

    模式匹配

    函数式编程里最酷的一件事就是模式匹配了. 事实上, 我也不确定为什么我这么喜爱它, 或许是因为它的简洁并且可以把问题打散为更小的部分. 例如: 在 Haskell 中, 可以这样定义阶乘算法:

    factorial 0 = 1
    factorial n = n * factorial (n - 1)

    看起来很酷吧. 你把一个巨大的任务分割成了一个个更小的部分, 并且将方案变得更加简单. 当我开始写这篇文章的时候, 我已经有了在JavaScript中实现类似功能的想法, 但是后来我发现它已经有了实现方案. 下面就是来自于 Bram Stein 的 funcy 的例子:

    var $ = fun.parameter,
        fact = fun(
            [0, function ()  { return 1; }],
            [$, function (n) { return n * fact(n - 1); }]
        );

    在 fun 内部有一个特殊的参数变量 - fun.parameter. 当通过空字符串 "" 调用 fact 时, function () { return 1; } 就会被执行, 否则执行 function (n) { return n * fact(n – 1); }, 是不是很酷?

    出处:http://www.cnblogs.com/m2maomao/p/5073107.html

  • 相关阅读:
    imperva_waf导入ssl证书
    博科光纤交换机初始化配置
    xss测试代码
    生成树注意事项
    [转载]Basics of the Unix Philosophy
    [转载]GSview注册码
    [转载]tar命令详解
    [转载]WinEdt 6 注册 试用期30天永不过期
    [转载+修改]计数排序
    [转载]C++ 关于声明,定义,类的定义,头文件作用,防止头文件在同一个编译单元重复引用,不具名空间
  • 原文地址:https://www.cnblogs.com/zhou195/p/6568275.html
Copyright © 2020-2023  润新知