内容要点:

本节讨论的是一种函数变换技巧,即把一次完整的函数调用拆成多次函数调用,每次传入的实参都是完整实参的一部分,每个拆分开的函数叫做不完全函数(partial function),每次函数调用叫做不完全调用(partial application),这种函数变换的特点是每次调用都返回一个函数,直到得到最终运行结果为止,举一个简单的例子,将对函数f(1,2,3,4,5,6)的调用修改为等价的f(1,2)(3,4)(5,6),后者包含三次调用,和每次调用相关的函数就是"不完全函数"。

一.

1.函数f()的bind()方法返回一个新函数,给新函数传入特定的上下文和一组指定的参数,然后调用f()。

我们说它把函数 "绑定至" 对象并传入一部分参数。bind()方法只是将实参放在(完整实参列表的)左侧,也就是说传入bind()的实参都是放在传入原始函数的实参列表开始的位置,但有时我们期望将传入bind()的实参放在(完整实参列表的)右侧:

//实现一个工具函数将类数组对象(或对象)转换为真正的数组

//在后面的示例代码中用到了这个方法将arguments对象转换为真正的数组

function array(a,n){ return Array.prototype.slice.call(a,n||0); }

//这个函数的实参传递至左侧

function partialLeft(f /*,...*/){

var args = arguments; //保存外部的实参数组

return function(){   //并返回这个函数

var a= array(args,1); //开始处理外部的第1个args

a = a.concat(array(arguments)); //然后增加所有的内部实参

return f.apply(this,a); //然后基于这个实参列表调用f()

};

}

//这个函数的实参传递至右侧

function partialRight(f/*,...*/){

var args = arguments; //保存外部实参数组

return function(){

var a = array(arguments); //从内部参数开始

a = a.concat(array(args,1)); //然后从外部第1个args开始添加

return f.apply(this,a); //最后基于这个实参列表调用f()

};

}

//这个函数的实参被用做模板

//实参列表中的undefined值都被填充

function partial(f/*,...*/){

var args = arguments; //保存外部实参数组

return function(){

var a = array(args,1); //从外部args开始

var i = 0,j =0;

//遍历args,从内部实参填充undefined值

for(;i<a.length;i++)

if(a[i] === undefined) a[i] = arguments[j++];

//现在将剩下的内部实参都追加进去

a = a.concat(array(arguments,j))

return f.apply(this,a);

};

}

//这个函数带有三个实参

var f = function(x,y,z){ return x*(y-z); };

//注意这三个不完全调用之间的区别

partialLeft(f,2)(3,4)  //=>-2:绑定第一个实参:2*(3 - 4

partialRight(f,2)(3,4) //=>6:绑定最后一个实参:3*(4 -2)

partial(f,undefined,2)(3,4) //=>-6:绑定中间的实参:3*(2-4)

二.利用已有的函数来定义新的函数

利用这种不完全函数的编程技巧,可以编写一些有意思的代码,利用已有的函数来定义新的函数:

var increment = partialLeft(sum,1);

var cuberoot = partialRight(Math.pow,1/3);

String.prototype.first = partial(String.prototype.charAt,0);

String.prototype.last = partial(String.prototype.substr,-1,1);

当将不完全调用和其他高阶函数整合在一起的时候,事情就变得格外有趣了。比如,这里的例子定义了not()函数,它用到了刚才提到的不完全调用:

var not = partialLeft(compose,function(x){ return !x;});

var even = function(x){ return x%2 ==0; };

var odd = not(even);

var isNumber = not(isNaN);

我们也可以使用不完全调用的组合来重新组织平均数和标准差的代码,这种编程风格是非常纯粹的函数式编程:

var data = [1,1,3,5,5]; //我们要处理的数据

var sum = function(x,y){ return x+y; };  //两个初等函数

var product = function(x,y){ return x*y; };

var neg = partial(product,-1);

var square = partial(Math.pow,undefined,2);

var sqrt = partial(Math.pow,undefined, .5);

var reciprocal = partial(Math.pow,undefined,-1);

//现在计算平均值和标准差,所有的函数调用都不带运算符

var mean =product(reduce(data,sum),reciprocal(data.length));

var stddev = sqrt(product(reduce(map(data,compose(square,partial(sum,neg(mean)))),sum),reciprocal(sum(data.length,-1))));

console.log(mean);//datad的平均数 3
       console.log(neg(mean));//平均数*(-1): -2 :mean*-1
       console.log(partial(sum,neg(mean)));//sum里的y是-3:即是负的平均数
       console.log(compose(square,partial(sum,neg(mean))));//sum里的y是-3:函数式:(data-3)^2,compose:组合两个函数,见8.8.2高阶函数
       console.log(Array.map(data,compose(square,partial(sum,neg(mean)))));//[4, 4, 0, 4, 4]: 即是平方差
       console.log(Array.reduce(Array.map(data,compose(square,partial(sum,neg(mean)))),sum));//16:即是平方差的求和
       
       console.log(reciprocal(sum(data.length,-1))); //0.25
       console.log(product(Array.reduce(Array.map(data,compose(square,partial(sum,neg(mean)))),sum),reciprocal(sum(data.length,-1)))); //4
       console.log(sqrt(product(Array.reduce(Array.map(data,compose(square,partial(sum,neg(mean)))),sum),reciprocal(sum(data.length,-1)))));//2

细细体会,跟数学式子差不多!

《JS权威指南学习总结--8.8.3 不完全函数》的更多相关文章

  1. 简单物联网:外网访问内网路由器下树莓派Flask服务器

    最近做一个小东西,大概过程就是想在教室,宿舍控制实验室的一些设备. 已经在树莓上搭了一个轻量的flask服务器,在实验室的路由器下,任何设备都是可以访问的:但是有一些限制条件,比如我想在宿舍控制我种花 ...

  2. 利用ssh反向代理以及autossh实现从外网连接内网服务器

    前言 最近遇到这样一个问题,我在实验室架设了一台服务器,给师弟或者小伙伴练习Linux用,然后平时在实验室这边直接连接是没有问题的,都是内网嘛.但是回到宿舍问题出来了,使用校园网的童鞋还是能连接上,使 ...

  3. 外网访问内网Docker容器

    外网访问内网Docker容器 本地安装了Docker容器,只能在局域网内访问,怎样从外网也能访问本地Docker容器? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Docker容器 ...

  4. 外网访问内网SpringBoot

    外网访问内网SpringBoot 本地安装了SpringBoot,只能在局域网内访问,怎样从外网也能访问本地SpringBoot? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装Java 1 ...

  5. 外网访问内网Elasticsearch WEB

    外网访问内网Elasticsearch WEB 本地安装了Elasticsearch,只能在局域网内访问其WEB,怎样从外网也能访问本地Elasticsearch? 本文将介绍具体的实现步骤. 1. ...

  6. 怎样从外网访问内网Rails

    外网访问内网Rails 本地安装了Rails,只能在局域网内访问,怎样从外网也能访问本地Rails? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Rails 默认安装的Rails端口 ...

  7. 怎样从外网访问内网Memcached数据库

    外网访问内网Memcached数据库 本地安装了Memcached数据库,只能在局域网内访问,怎样从外网也能访问本地Memcached数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装 ...

  8. 怎样从外网访问内网CouchDB数据库

    外网访问内网CouchDB数据库 本地安装了CouchDB数据库,只能在局域网内访问,怎样从外网也能访问本地CouchDB数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Cou ...

  9. 怎样从外网访问内网DB2数据库

    外网访问内网DB2数据库 本地安装了DB2数据库,只能在局域网内访问,怎样从外网也能访问本地DB2数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动DB2数据库 默认安装的DB2 ...

  10. 怎样从外网访问内网OpenLDAP数据库

    外网访问内网OpenLDAP数据库 本地安装了OpenLDAP数据库,只能在局域网内访问,怎样从外网也能访问本地OpenLDAP数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动 ...

随机推荐

  1. codewars-random(4)

    问题简介:算多长时间用本金能得到想要的利息,基础题. 代码:function calculateYears(principal, interest, tax, desired) {    // you ...

  2. 【原】手写一个promise

    上一篇文章中,我们介绍了Promise的基本使用,在这篇文章中,我们试着自己来写一个Promise,主要是学习Promise的内部机制,学习它的编程思想. !!!备注:本文写的不好,仅供自己学习之用, ...

  3. vs 插件

    Visual Assist 代码提示 Indent Guides

  4. 一步步优化JVM六:优化吞吐量

    如果你已经进行完了前面的步骤了,那么你应该知道这是最后一步了.在这一步里面,你需要测试应用的吞吐量和为了更高的吞吐量而优化JVM.    这一步的输入就是应用的吞吐量性能要求.应用的吞吐量是在应用层面 ...

  5. Word 2016插入公式快捷键

    实用的插入公式快捷键"Alt+=", 与君共享

  6. vultr新用户注册享受50美元优惠码,长期有效

    vultr vps服务器,我用了三年多,购买了几十台vps,性价比非常高. 近期,vutlr推出了最新优惠码DOMORE长期有效,新用户注册账号时候,可在付款方式界面输入这个优惠码,享受50美元余额, ...

  7. 前端之Photoshop切片

    什么是切片 ?     (Photoshop中的切片) 切片:将图片切成几部分,一片一片往上传,这样上传的速度比较快.每个切片作为一个独立的文件传输,文件中包含切片自己的设置.颜色调板.链接.翻转效果 ...

  8. iOS-延迟操作方法总结

    在实际应用中,有时候我们会需要延时执行某些操作,所以我们这里总结了四种延迟操作的方法,并简要分析了每种方法的异同. NSObject的相关方法 第一种方法是使用NSObject类的performSel ...

  9. bat脚本学习

    工作需求,写个bat脚本来启动自己的守护进程:bat方面完全空白啊~稍微学习了下,记录下来,后面复习! 直接上代码: 示例一:for字符串切割,切割文本中的字符串: test.txt 文本内容如下: ...

  10. php 中数据类型

    总体划分 8 中 1基本类型(标量) 整型 int 整型的三种写法 <?php $n1 = 123; //10进制 $n2 = 0123; //8进制 $n3 = 0x123;//16进制 // ...