假期就这么结束了!十天假就有三天在路上,真的难受!想想假期除了看了两场电影貌似也没做什么深刻印象的事情。流浪地球,特效还是很赞,不过对于感情的描写还是逃不掉拖沓和尴尬的通病,对于国产科幻还是抱有支持的态度。疯狂的外星人相比读大学期间看的疯狂的赛车,荒诞感还是差了点,也许是我笑点太高...不过整体还是感觉比流浪地球值票价,个人观点吧。

开年来同事说自己小舅子年终奖税后18W,他这两天深受打击,我听完也深受打击,哎。

假期结束也该好好安排下今年的时间了,年底辞职的规划不变,加上未来几年要结婚,想想还有很多东西没学习压力还是很大,为了多挣点钱加油吧!

 一、Memoization模式

函数是对象,我们可以为函数添加属性,或者读取它自带的属性,比如函数都有一个length属性,此属性表示函数参数的长度。例如:

function demo (a, b, c) {};
console.log(demo.length);//

为函数添加自定义属性有一个很好用的场景就是缓存函数运行结果(返回值),这样下次执行时就不用重复的去执行那些复杂的计算,这种做法就是在编程中常用的Memoization模式。

const myFunc = function (param) {
let result;
result = param * 2;
if (!myFunc.cache[param]) {
myFunc.cache[param] = result;
};
};
myFunc.cache = {};
//调用函数
myFunc(1);
console.log(myFunc.cache); //{1: 2}
myFunc(2);
console.log(myFunc.cache); //{1: 2, 2: 4}
//直接获取对应形参的执行结果
console.log(myFunc.cache[1]);//2 7 4

在上述代码中我们定义了一个myFunc函数,它主要接受一个参数param,并做将参数乘以2的操作,这里先不考虑param不为number类型的情况。

同时我们为myFunc添加了一个cache属性,是一个对象,每次函数调用执行,参数都会成为cache属性的key,函数运算结果会成为cache对应的value,这样就缓存了每次运算的键值对运算结果。

假设运算,或者逻辑远比*2复杂,那么当我们下载又想知道当传入参数2的结果时,只需要通过myFunc.cache[2]访问对应的value即可,避免的重复的复杂运算。

二、配置对象(函数形参对象写法)

正常开发中程序的维护以及修改是无法避免的问题,比如一个简单的需求,随着时间的推移,越来越多的功能被加进来。

假设我们有一个负责添加联系人的函数,接受姓和名两个参数。

function addPerson (first, last) {}

现在需求变了,除了姓名,你还得保存性别,地址等可选信息,于是我们给函数形参添加了2个参数,因为是可选,我们还得将参数放在后面一点。

function addPerson (first, last, gender, address) {}

当我们调用时,我们得保证参数传递顺序与函数形参一致,且必要的形参写在前面。

addPerson("echo", 'lun', null, '深圳');

这样就是比较麻烦的,好的做法就是将函数形参替换为一个参数,且此参数是一个对象,我们称之为conf,也就是配置英文单词的缩写。

function addPerson(param) {
let userName = param.first + param.last,
userAddress = param.address;
};
let conf = {
gender: '',
first: 'echo',
address: '深圳',
last: 'lun'
};
addPerson(conf);

这样做的好处是,调用者不需要记住参数的顺序,可以更方便的跳过可选参数,由于形参变少,提升可读性的同时,也增加了可维护性,毕竟增删参数更加简单。

但缺点是,需要记住参数的名称,毕竟函数内部是通过参数作为key来访问的,其次是参数名不可压缩。此模式对于修改CSS样式的函数非常实用,因为CSS样式一般较多,并且存在可选属性。

三、函数柯里化

1.函数应用

如何使用一个函数,最为直观的是函数调用,但在一些纯粹的函数式编程中,对函数的描述不是被调用,而是被应用。

在JS中,函数除了调用,我们也能通过Function.prototype.aplly()来应用一个函数。因为函数本身就是对象,它们也有自己的方法

let sayHi = function (who) {
return "hello" + (who ? "," + who : "") + "!";
};
//函数调用
sayHi(); //hello!
sayHi('echo') //hello,echo!
//函数应用
sayHi.apply(null, ["时间跳跃"]);//hello,时间跳跃!

上述代码中不管调用或是应用函数,都能得到预期的结果。在函数应用中,apply()接受两个参数,第一个参数是函数内部this所绑定的对象,第二个参数是一个参数数组,参数数组在函数内部会变成类数组arguments对象。如果第一个参数为null,那么this会指向全局对象。

但如果一个函数是一个对象的方法时,第一个参数不会传递null,这样做的目的是保证方法中的this绑定到一个有效对象,在下方代码中,this指向了alien。

let alien = {
sayHi: function (who) {
console.log("hello" + (who ? "," + who : "") + "!");
}
}; //函数调用
alien.sayHi('echo') //hello,echo!
//函数应用
alien.sayHi.apply(alien, ["时间跳跃"]); //hello,时间跳跃!

事实上函数调用属于函数应用的一种语法糖,函数应用出了apply()之外,还有提供一个call()方法,但它仍然只是apply()的一种语法糖。

apply()与call()两者的区别在于,apply()只接受两个参数,第一个为函数this绑定的对象,第二参数为函数参数数组。相比之下,call()可接受任意多的参数,第一个参数为this绑定对象,从二个参数开始将依次传递给函数。

假设函数只有一个参数,使用call()要比apply()要更加优化,因为这样可以省去一个创建数组的步骤。

 2.函数部分应用

现在我们可以说,调用一个函数本质是给函数应用了一堆参数,假设有一个处理两数相加的函数,我们其实可以按函数应用的思想将步骤拆分出来。

function add(x, y) {
return x + y;
};
add(5, 1);
//按函数应用的思想拆分运行步骤
//第一步
function add(5, y){
return 5 + y;
};
// 第二步
function add(5, 4){
return 5 + 4;
};

虽然上述代码中步骤1与2并不是真正有效的代码,但大概表达了这个意思。先应用了参数5,替换了函数内部变量,然后重复此过程,直到替换所有参数,计算得到最终结果。

步骤1中我们只应用了参数5,并未得到最终计算结果,反而得到了一个替换了部分参数的另一个函数,我们可以称此函数为add函数的部分应用(partialApply)。

假设现在我们有一个虚拟的部分应用函数partialApply(),来看一段代码

let add = function(x, y) {
return x + y;
};
// 正常的函数应用
add.apply(null, [5, 4]); //
//部分应用拆分
let newAdd = add.partialApply(null, [5]);
newAdd.apply(null,[4]);//

函数应用是分步骤的,我们可以说调用函数add(5,4)的写法是add(5)(4)一种语法糖,本质是相同的。

当然由于上述代码中partialApply()函数是我们假想的,add(5)(4)这样的写法也是会报错的,这里只是为了传达一个概念,让函数理解并且处理部分应用的过程叫柯里化(Currying)。

3.函数柯里化

柯里化是一个变换函数的过程,还是上面add函数,我们尝试修改下,实现我们想要的柯里化。

let add = function(x, y) {
let oldX = x,
oldY = y;
if(oldY === undefined){
return function (newY) {
return oldX + newY;
};
};
return x + y;
};
//普通调用
add(5,4)//
//分步骤调用
add(5)(4);//

实现上利用了闭包的思想,当调用add(5)时我们其实得到了一个全新的函数,第二次调用add(4)时得到了最终结果,当然这个实现有点尴尬,内部还创建了三个意义不大的变量oldX,oldY与newY,只是为了方便理解,我们改改。

let add = function(x, y) {
if(y === undefined){
return function (y) {
return x + y;
};
};
return x + y;
};
//普通调用
add(5,4)//
//分步骤调用
add(5)(4);//

这样就精简了很多,也达到了我们需要的效果。但是有个问题,这样的写法不够复用,仅仅是写在了add函数内部供add自身使用,我们能不能封装出一个较为共用的方法,让任意函数都能柯里化呢。

//定义通用的柯里化函数
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);
};
};
//定义add函数
function add(x, y) {
return x + y;
};
//开始调用
let newAdd = schonfinkelize(add, 5);
newAdd(4);//
schonfinkelize(add, 5)(4);//

在上述代码中,我们定义了一个通用的柯里化函数schonfinkelize,为什么叫这个名,书上说难发音,难记,有代表性....

在schonfinkelize中,我们通过Array.prototype.slice.call()方法将类数组arguments转为数组,在第一次调用时我们传递了add与数字5两个参数,将5存入变量stored_args的同时,并返回了一个全新的函数。

在第二次调用时我们传递了参数4,通过数组concat方法,将前后两次的参数合并成了新数组[5,4]作为了fn,也就是add函数运行时所需要的函数。

4.什么时候使用柯里化

看到这问题就来了,这不神经病吗,好好的调用干嘛非得拆成几步,意义在哪呢?我们什么情况下要使用函数柯里化呢?存在即合理,当我们调用一个函数多次,传入的参数大部分相同时,我们就可以通过传递一部分的参数动态创建一个新的函数,这个新函数会存储那些重复的参数,这样你就不用反复的传入,执行相同的步骤,然后再在此函数的基础上补全别的参数,达到最终执行的目的。

假设还是这个add函数,计算两数之和,我们要分别计算3+4和3+5的合,利用柯里化,我们可以将参数3的状态形成一个新的函数,在分别补全4和5的参数,得到两次计算的结果。(看到这我也是惊叹了)

let newAdd = schonfinkelize(add, 3);
newAdd(4)//
newAdd(5)//

那么到这里,函数这一章节就算读完了,这里做个简单的小总结:

函数是一等公民,它可以作为值传递,可以作为函数的返回值,也可以拥有属性和方法。函数拥有本地作用域,而大括号不产生会计作用域。

创建一个函数有三种方法,函数声明,函数表达式,以及构造函数new创建。

在介绍完函数后,也提到了函数的一些有趣的模式,比如回调模式,将函数作为参数传递给另一个函数,而对于函数的参数控制,我们也说了配置对象,通过对象的写法,让函数的参数更易可读与维护。函数可以返回一个函数,比如闭包,最后我们介绍了函数应用与函数柯里化。

初始化模式是我们在日常开发中一种干净结构化的方法,例如立即执行函数,达到初始化的目的,也避免了污染全局。而初始化我们大部分希望只执行一次,不需要反复的调用,所以我们也介绍了条件初始化。

最后我们介绍了一些性能模式,比如Memoization模式来记忆复杂的运算结果,提高代码的执行效率。自定义函数的重写模式,让第二次使用时能做更少的工作等等。

从这章开始,提到的一些东西我也觉得渐渐有趣起来了,下一章节是关于对象的创建模式,反正这两个月把这本书读完记录完,加油吧。

精读JavaScript模式(六),Memoization模式与函数柯里化的应用的更多相关文章

  1. 一道javascript面试题(闭包与函数柯里化)

    要求写一个函数add(),分别实现能如下效果: (1)console.log(add(1)(2)(3)(4)()); (2)console.log(add(1,2)(3,4)()); (3)conso ...

  2. javascript中bind绑定接收者与函数柯里化

    如果我要遍历一个数组, 我只要给forEach传一个匿名函数即可,很简单: let arr = ['a', 'b', 'c']; arr.forEach((item, index) => { c ...

  3. JavaScript函数柯里化的一些思考

    1. 高阶函数的坑 在学习柯里化之前,我们首先来看下面一段代码: var f1 = function(x){ return f(x); }; f1(x); 很多同学都能看出来,这些写是非常傻的,因为函 ...

  4. Javascript函数柯里化(curry)

    函数柯里化currying,是函数式编程非常重要的一个标志.它的实现需要满足以下条件,首先就是函数可以作为参数进行传递,然后就是函数可以作为返回值return出去.我们依靠这个特性编写很多优雅酷炫的代 ...

  5. 深入理解javascript函数进阶系列第二篇——函数柯里化

    前面的话 函数柯里化currying的概念最早由俄国数学家Moses Schönfinkel发明,而后由著名的数理逻辑学家Haskell Curry将其丰富和发展,currying由此得名.本文将详细 ...

  6. 简单粗暴详细讲解javascript实现函数柯里化与反柯里化

    函数柯里化(黑人问号脸)???Currying(黑人问号脸)???妥妥的中式翻译既视感:下面来一起看看究竟什么是函数柯里化: 维基百科的解释是:把接收多个参数的函数变换成接收一个单一参数(最初函数的第 ...

  7. JavaScript中的事件循环机制跟函数柯里化

    一.事件循环机制的理解 test();//按秒输出5个5 function test() { for (var i = 0; i < 5; i++) { setTimeout(() => ...

  8. JavaScript之函数柯里化

    什么是柯里化(currying)? 维基百科中的解释是:柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术.意思就是当函 ...

  9. 简单粗暴详细讲解javascript实现函数柯里化

    函数柯里化(黑人问号脸)???Currying(黑人问号脸)???妥妥的中式翻译既视感:下面来一起看看究竟什么是函数柯里化: 维基百科的解释是:把接收多个参数的函数变换成接收一个单一参数(最初函数的第 ...

随机推荐

  1. Aria2+WebUI,迅雷倒下之后的代替品

    Aria2+WebUI,迅雷倒下之后的代替品 (2017-07-24 12:56:28) 转载▼   分类: 软件 最近迅雷越来越作死了,砍第三方远程下载,强推迅雷9喂用户的屎,下载资源能砍就砍,以前 ...

  2. 描述符__get__,__set__,__delete__

    描述符__get__,__set__,__delete__ # 描述符:1用来代理另外一个类的属性 # __get__():调用一个属性时,触发 # __set__():为一个属性赋值时触发 # __ ...

  3. 设计模式学习心得<汇总>

    绝大部分程序员其实用不上设计模式. - 创建 结构 行为 描述 在软件工程中,创建型模式是处理对象创建的设计模式,试图根据实际情况使用合适的方式创建对象.基本的对象创建方式可能会导致设计上的问题,或增 ...

  4. CDH版本的hadoop下载

    http://archive.cloudera.com/cdh5/cdh/5/加要下载的cdh版本插件 例如: http://archive.cloudera.com/cdh5/cdh/5/zooke ...

  5. android 测试

    Android Monkey压力测试 monkey测试结果详细分析 Android自动化测试工具 UiAutomator使用详解 在Android Sudio中使用Uiautomator 六款Andr ...

  6. 在Unity5.6.5f1中使用C#7语法

    备忘,记忆力越来越差了,必需把这种琐碎的东西记下来,以防1年后想再用完全没头绪. 之前试过用C#6语法,但是怎么配置操作的完全没印象了. 首先去这下载扩展 https://bitbucket.org/ ...

  7. boost--时间处理

    date_time库的时间功能位于名字空间boost::posix_time,它提供了微妙级别(最高可达纳秒)的时间系统,使用需要包含头文件"boost\date_time\posix_ti ...

  8. div辅助线【完整版】

    ## <html> <head> <link rel="stylesheet" type="https://cdn.bootcss.com/ ...

  9. Python之旅Day2 元组 字符串 字典 集合

    元组(tuple) 元组其实跟列表差不多,也是存一组数,与列表相比,元组一旦创建,便不能再修改,所以又叫只读列表. 语法: names = ("Wuchunwei","Y ...

  10. Django基础和基本使用

    Django基础 Django是Python下的一款著名的Web框架 框架 任何语言进入到高级部分时,会有认证.session.http.连接数据库等等功能操作,没有框架时需要自己实现 框架 是整个或 ...