资讯专栏INFORMATION COLUMN

JavaScript函数式编程(1):基本思想

y1chuan / 2607人阅读

摘要:中的函数式编程思想匿名函数在函数式编程语言中,函数是可以没有名字的,匿名函数通常表示可以完成某件事的一块代码。匿名函数中包含对的局部变量的引用,因此当返回时,的值被保留不会被垃圾回收机制回收,持续调用,将会改变的值。

1 函数式编程简介

函数式编程是和传统命令式编程区分的一种编程思想,“在函数式编程语言中,函数是第一类的对象,也就是说,函数 不依赖于任何其他的对象而可以独立存在,而在面向对象的语言中,函数 ( 方法 ) 是依附于对象的,属于对象的一部分。这一点决定了函数在函数式语言中的一些特别的性质,比如作为传出 / 传入参数,作为一个普通的变量等。[1]”

函数式编程思想的源头可以追溯到 20 世纪 30 年代,数学家阿隆左 . 丘奇在进行一项关于问题的可计算性的研究,也就是后来的 lambda 演算。lambda 演算的本质为 一切皆函数,函数可以作为另外一个函数的输出或者 / 和输入,一系列的函数使用最终会形成一个表达式链,这个表达式链可以最终求得一个值,而这个过程,即为计算的本质。

然而,这种思想在当时的硬件基础上很难实现,历史最终选择了同丘奇的 lambda 理论平行的另一种数学理论:图灵机作为计算理论,而采取另一位科学家冯 . 诺依曼的计算机结构,并最终被实现为硬件。由于第一台计算机即为冯 . 诺依曼的程序存储结构,因此运行在此平台的程序也继承了这种基因,程序设计语言如 C/Pascal 等都在一定程度上依赖于此体系。

到了 20 世纪 50 年代,一位 MIT 的教授 John McCarthy 在冯 . 诺依曼体系的机器上成功的实现了 lambda 理论,取名为 LISP(LISt Processor), 至此函数式编程语言便开始活跃于计算机科学领域。

2 JavaScript中的函数式编程思想 2.1)匿名函数

在函数式编程语言中,函数是可以没有名字的,匿名函数通常表示:“可以完成某件事的一块代码”。这种表达在很多场合是有用的,因为我们有时需要用函数完成某件事,但是这个函数可能只是临时性的,那就没有理由专门为其生成一个顶层的函数对象。

    //自定义map()
    function map(array, func){ 
        var res = []; 
        for ( var i = 0, len = array.length; i < len; i++){ 
                res.push(func(array[i])); 
            } 
        return res; 
    } 
    //匿名函数
    var mapped = map([1, 3, 5, 7, 8],  function (n){ 
        return n = n + 1; 
    }); 
    alert(mapped); //2,4,6,8,9// 对数组 [1,3,5,7,8] 中每一个元素加 1
2.2)柯里化

是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,或者说创建一个已经预先包含一个参数的函数,这其中会使用到闭包。
先来一个不是类似克里化的例子:

function add(num1, num2){
        return num1 + num2;
    }
    function curriedAdd(num2){
        return add(5, num2);
    }
    alert(add(2, 3)); //5
    alert(curriedAdd(3)); //8

这里的curriedAdd函数类似克里化,因为它固定了函数的一个参数。
下面看一个真正的函数克里化:

  //传入的参数不止fn一个,但是只有fn显式用到,所以明确给出

    function curry(fn){
            var args = Array.prototype.slice.call(arguments, 1);//得到外部函数参数
            return function(){
            var innerArgs = Array.prototype.slice.call(arguments);//得到内部函数参数
            var finalArgs = args.concat(innerArgs);//参数合并得到整体参数数组
            return fn.apply(null, finalArgs);//对整体参数数组运用函数fn
        };
    }
    function add(num1, num2){
        return num1 + num2;
    }
    var curriedAdd = curry(add, 5);
    alert(curriedAdd(3)); //8

根据上述实例,克里化一般通过curry()函数动态实现,curry()函数的主要工作就是将被返回函数的参数进行排序。 curry()的第一个参数是要进行柯里化的函数,其他参数是要传入的值。为了获取第一个参数之后的所有参数,在 arguments 对象上调用了 slice()方法,并传入参数 1 表示被返回的数组包含从第二个参数开始的所有参数。然后 args 数组包含了来自外部函数的参数。在内部函数中,创建了 innerArgs 数组用来存放所有传入的参数(又一次用到了 slice())。有了存放来自外部函数和内部函数的参数数组后,就可以使用 concat()方法将它们组合为 finalArgs,然后使用 apply()将结果传递给该函数。注意这个函数并没有考虑到执行环境,所以调用 apply()时第一个参数是 null。

2.3)高阶函数

即为对函数的进一步抽象,map(array, func) 的表达式已经表明,将 func 函数作用于 array 中的每一个元素,最终返回一个新的 array,应该注意的是,map 对 array 和 func 的实现是没有任何预先的假设的(抽象),因此称之为“高阶”函数。

    function map(array, func){ 
      var res = []; 
      for ( var i = 0, len = array.length; i < len; i++){ 
             res.push(func(array[i])); 
         } 
          return res; 
     } 
     var mapped = map([1, 3, 5, 7, 8],  function (n){ 
          return n = n + 1; 
     }); 
    alert(mapped); 
    
    var mapped2 = map(["one", "two" , "three", "four"], 
      function (item){ 
          return "("+item+")"; 
     }); 
    alert(mapped2);

mapped 和 mapped2 均调用了 map,但是得到了截然不同的结果,因为 map 的参数本身已经进行了一次抽象,map 函数做的是第二次抽象,高阶的“阶”可以理解为抽象的层次。

2.4)闭包

闭包已经在另一篇文章《闭包:私有化变量》中介绍过,与闭包有关的垃圾回收机制在《垃圾回收机制——总结自《JavaScript高级程序设计》也有详细介绍,因此这里仅仅简要说明闭包。
当在一个函数 outter 内部定义另一个函数 inner,而 inner 又引用了 outter 作用域内的变量,在 outter 之外使用 inner 函数,则形成了闭包。

   

匿名函数 function(){return n++;} 中包含对 outter 的局部变量 n 的引用,因此当 outter 返回时,n 的值被保留 ( 不会被垃圾回收机制回收 ),持续调用 o1(),将会改变 n 的值。而 o2 的值并不会随着 o1() 被调用而改变,第一次调用 o2 会得到 n==0 的结果,用面向对象的术语来说,就是 o1 和 o2 为不同的 实例,互不干涉。
闭包实例2:

//创建一个对象并压入数组
var outter = [];
function clouseTest () {
var array = ["one", "two", "three", "four"];
for ( var i = 0; i < array.length;i++){

var x = {}; 
x.no = i; 
x.text = array[i]; 
 x.invoke =  function (){ 
     alert(i); 
    } 
outter.push(x); 
} 

}
//执行函数并调用每个数组元素对应的对象的方法
clouseTest();// 调用这个函数,向 outter 数组中添加对象
for ( var i = 0, len = outter.length; i < len; i++){

outter[i].invoke(); //4,4,4,4

}

由于 i 是闭包中的局部变量,for 循环最后退出时的值为 4,因此调用 outter 中的每个元素都会得到 4。
修正过后的闭包:

       

[1] http://www.ibm.com/developerw...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/50137.html

相关文章

  • JavaScript函数编程1):基本思想

    摘要:中的函数式编程思想匿名函数在函数式编程语言中,函数是可以没有名字的,匿名函数通常表示可以完成某件事的一块代码。匿名函数中包含对的局部变量的引用,因此当返回时,的值被保留不会被垃圾回收机制回收,持续调用,将会改变的值。 1 函数式编程简介 函数式编程是和传统命令式编程区分的一种编程思想,在函数式编程语言中,函数是第一类的对象,也就是说,函数 不依赖于任何其他的对象而可以独立存在,而在面向...

    时飞 评论0 收藏0
  • javascript 函数编程思想

    摘要:今天这篇文章主要介绍函数式编程的思想。函数式编程通过最小化变化使得代码更易理解。在函数式编程里面,组合是一个非常非常非常重要的思想。可以看到函数式编程在开发中具有声明模式。而函数式编程旨在尽可能的提高代码的无状态性和不变性。 最开始接触函数式编程的时候是在小米工作的时候,那个时候看老大以前写的代码各种 compose,然后一些 ramda 的一些工具函数,看着很吃力,然后极力吐槽函数式...

    YPHP 评论0 收藏0
  • 编程模型(范)小结

    摘要:参考链接面向对象编程模型现在的很多编程语言基本都具有面向对象的思想,比如等等,而面向对象的主要思想对象,类,继承,封装,多态比较容易理解,这里就不多多描述了。 前言 在我们的日常日发和学习生活中会常常遇到一些名词,比如 命令式编程模型,声明式编程模型,xxx语言是面向对象的等等,这个编程模型到处可见,但是始终搞不清是什么?什么语言又是什么编程模型,当你新接触一门语言的时候,有些问题是需...

    miya 评论0 收藏0
  • SegmentFault 技术周刊 Vol.16 - 浅入浅出 JavaScript 函数编程

    摘要:函数式编程,一看这个词,简直就是学院派的典范。所以这期周刊,我们就重点引入的函数式编程,浅入浅出,一窥函数式编程的思想,可能让你对编程语言的理解更加融会贯通一些。但从根本上来说,函数式编程就是关于如使用通用的可复用函数进行组合编程。 showImg(https://segmentfault.com/img/bVGQuc); 函数式编程(Functional Programming),一...

    csRyan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<