内容要点:

本节讨论的是一种函数变换技巧,即把一次完整的函数调用拆成多次函数调用,每次传入的实参都是完整实参的一部分,每个拆分开的函数叫做不完全函数(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. CentOS 7 引导 -- GRUB2

    如果安装完 CentOS 7 之后,原来的 Windows 引导菜单被覆盖了,vi /boot/grub2/grub.cfg 添加如下代码: ## BEGIN WINDOWS 10 menuentry ...

  2. 使用Egret Conversion 转化as代码到ts代码

    1,转换时,如果一次转化代码文件太多,可能会出现错误,可以一次少转几个文件: 2,如果出现所转换文件中有错误,则需修改要转换文件: 3,转换时还可能生成新文件夹如elex,转换的ts文件就会存在ele ...

  3. java生成图片

    在一张图片上绘制别的图片以及文字. public String GenerateImage(WebCast_baseinfo base,String code,String customName,St ...

  4. Laravel生成编译文件权限问题

    sudo -unobody ./artisan optimize # 避免`fpm`账户无权限修改缓存文件的问题.这里的`nobody`改成实际的`fpm`用户名.

  5. RandomAccessFile类初次使用

    RandomAccessFile : java提供的对文件内容的访问 既可以读文件 也可以写文件 支持随机访问文件 可以访问文件的任意位置 (1)java文件模型 : 在硬盘上的文件是byte byt ...

  6. UITableView优化方案

    1.UITableView的简单认识 > UITableView最核心的思想就是UITableViewCell的重用机制.简单的理解就是:UITableView只会创建一屏幕(或一屏幕多一点)的 ...

  7. 子查询 此处该用AND 而不是 WHERE

    条件:有一张账户表,一张订单表. 需求:求出所有role = 2 即客服人员,所有操作成功的订单数量.结果:能查出所有的客服人员名称,以及操作的订单数量(关键点在于,没有操作过订单,则数量显示为0) ...

  8. 基于AFN的多张图片上传

    不废话,直接上代码 NSString *urlString = [NSString stringWithFormat:@"http://192.168.1.166:8080/Discipli ...

  9. JavaScript-Curry

    Currying allows you to easily create custom functions by partially invoking an existing function. He ...

  10. iOS TableView的分割线

    if ([self.tableView respondsToSelector:@selector(setSeparatorInset:)]) { [self.tableView setSeparato ...