转眼之间已入五月,本身毕业也立刻有三年了。大学计算机系的同窗大多都在北京混迹,你们为了升职加薪,娶媳妇买房,熬夜加班跟上线,出差pk脑残客户。同窗聚会时有很多兄弟已经体重飙升,开始关注13号地铁线上铺天盖地的植发广告。都说25岁是一个男人的分界线,以前是越活越精致,日后是越活越糙。如今体会到了。父母开始老去,本身尚一无全部,攒的钱不够买一平米的房。昨天和一哥们撸串,我问:有啥打算?哥们吞了几口羊肉串,喝了一口啤酒,说:存点钱吧,而后回家。javascript
说实话,我之前的想法也同样。奈何来北京容易,想走却很难。清明节回太原一趟,总觉的路上太过于寂静,你们走路速度太慢,商店关门太早,居然有些许不适应。兀的发觉,北京肉体虽然天天很疲惫,但灵魂力量却修炼的很强。回到昌平的20平出租屋内,心里暗想,继续混,混到混不下去为止。css
以前写过一篇博文《我在百度作外包》,没想到许多同窗深有同感。北京的外包群体是一个很大的社会组织结构,他们须要获得更多的关注。但生存是社会永恒的主题,外包人员更要懂得,一切以实力说话,没有技术就没有发言权。没有名校背景的同窗进大厂很难,反过来讲外包也是另一条路,只不过这条路须要付出更多的努力,机会永远留给有准备的人。html
这篇博文,做为我侥幸从外包转成正式百度员工的记念。前端
这是一个系列博客,但愿他能在个人码农生涯中留下些什么。java
闲话很少说,这篇文章主要和你们分析下前端的函数式编程思想。纲要以下:git
对于前端,全部的成员是一个集合,变形关系是函数。编程
1..函数式编程(Functional Programming)其实相对于计算机的历史而言是一个很是古老的概念,甚至早于第一台计算机的诞生。函数式编程的基础模型来源于 λ (Lambda x=>x*2)演算,而 λ 演算并不是设计于在计算机上执行,它是在 20 世纪三十年代引入的一套用于研究函数定义、函数应用和递归的形式系统。canvas
2.函数式编程不是用函数来编程,也不是传统的面向过程编程。主旨在于将复杂的函数符合成简单的函数(计算理论,或者递归论,或者拉姆达演算)。运算过程尽可能写成一系列嵌套的函数调用设计模式
3.JavaScript 是披着 C 外衣的 Lisp。数组
4.真正的火热是随着React的高阶函数而逐步升温。
5.函数是一等公民。所谓”第一等公民”(first class),指的是函数与其余数据类型同样,处于平等地位,能够赋值给其余变量,也能够做为参数,传入另外一个函数,或者做为别的函数的返回值。
6.不可改变量。在函数式编程中,咱们一般理解的变量在函数式编程中也被函数代替了:在函数式编程中变量仅仅表明某个表达式。这里所说的’变量’是不能被修改的。全部的变量只能被赋一次初值。
7.map & reduce他们是最经常使用的函数式编程的方法。
将上面的概念简述一下:
1. 函数是”第一等公民”
2. 只用”表达式",不用"语句"
3. 没有”反作用"
4. 不修改状态
5. 引用透明(函数运行只靠参数)
•纯函数
•函数的柯里化
•函数组合
•Point Free
•声明式与命令式代码
•核心概念
什么是纯函数呢?
对于相同的输入,永远会获得相同的输出,并且没有任何可观察的反作用,也不依赖外部环境的状态的函数,叫作纯函数。
举个栗子:
var xs = [1,2,3,4,5];// Array.slice是纯函数,由于它没有反作用,对于固定的输入,输出老是固定的 xs.slice(0,3); xs.slice(0,3); xs.splice(0,3);// Array.splice会对原array形成影响,因此不纯 xs.splice(0,3);
传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
咱们有这样一个函数checkage:
var min = 18;
var checkage = age => age > min;
这个函数并不纯,checkage 不只取决于 age还有外部依赖的变量 min。 纯的 checkage 把关键数字 18 硬编码在函数内部,扩展性比较差,柯里化优雅的函数式解决。
var checkage = min => (age => age > min);
var checkage18 = checkage(18); // 先将18做为参数,去调用此函数,返回一个函数age => age > 18;
checkage18(20);// 第二步,上面返回的函数去处理剩下的参数,即 20 => 20 > 18; return true;
// 柯里化以前 function add(x, y) { return x + y; } add(1, 2) // 3 // 柯里化以后 function addX(y) { return function (x) { return x + y; }; } addX(2)(1) // 3
为了解决函数嵌套过深,洋葱代码:h(g(f(x))),咱们须要用到“函数组合”,咱们一块儿来用柯里化来改他,让多个函数像拼积木同样。
const compose = (f, g) => (x => f(g(x))); var first = arr => arr[0]; var reverse = arr => arr.reverse(); var last = compose(first, reverse); last([1, 2, 3, 4, 5]); // 5
函数组合交换律,相似于乘法交换律:
const f = str => str.toUpperCase().split(' ');
var toUpperCase = word => word.toUpperCase(); var split = x => (str => str.split(x)); var f = compose(split(' '), toUpperCase); f("abcd efgh");
把一些对象自带的方法转化成纯函数,而后经过函数组合去调用,这种风格可以帮助咱们减小没必要要的命名,让代码保持简洁和通用。是否是很方便!
在咱们平常业务开发中,写的代码绝大多数都为命令式代码;
//命令式 let CEOs = []; for (var i = 0; i < companies.length; i++) { CEOs.push(companies[i].CEO) } //声明式 let CEOs = companies.map(c => c.CEO);
下面咱们再深刻一下,你们注意好好理解吸取:
高阶函数,就是把函数当参数,把传入的函数作一个封装,而后返回这个封装函数,达到更高程度的抽象。
//命令式 var add = function (a, b) { return a + b; }; function math(func, array) { return func(array[0], array[1]); } math(add, [1, 2]); // 3
// 不是尾递归,没法优化 function factorial(n) { if (n === 1) return 1; return n * factorial(n - 1); } function factorial(n, total) { if (n === 1) return total; return factorial(n - 1, n * total); } //ES6强制使用尾递归
咱们看一下递归和尾递归执行过程:
递归:
function sum(n) { if (n === 1) return 1; return n + sum(n - 1); }
sum(5) (5 + sum(4)) (5 + (4 + sum(3))) (5 + (4 + (3 + sum(2)))) (5 + (4 + (3 + (2 + sum(1))))) (5 + (4 + (3 + (2 + 1)))) (5 + (4 + (3 + 3))) (5 + (4 + 6)) (5 + 10) 15 // 递归很是消耗内存,由于须要同时保存不少的调用帧,这样,就很容易发生“栈溢出”
尾递归
function sum(x, total) { if (x === 1) { return x + total; } return sum(x - 1, x + total); }
sum(5, 0) sum(4, 5) sum(3, 9) sum(2, 12) sum(1, 14) 15
1.函数不只能够用于同一个范畴之中值的转换,还能够用于将一个范畴转成另外一个范畴。这就涉及到了函子(Functor)。
2.函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。它首先是一种范畴,也就是说,是一个容器,包含了值和变形关系。比较特殊的是,它的变形关系能够依次做用于每个值,将当前容器变造成另外一个容器。
$(...) 返回的对象并非一个原生的 DOM 对象,而是对于原生对象的一种封装,这在某种意义上就是一个“容器”(但它并不函数式)。
Functor(函子)遵照一些特定规则的容器类型。任何具备map方法的数据结构,均可以看成函子的实现。
Functor 是一个对于函数调用的抽象,咱们赋予容器本身去调用函数的能力。把东西装进一个容器,只留出一个接口 map 给容器外的函数,map 一个函数时,咱们让容器本身来运行这个函数,这样容器就能够自由地选择什么时候何地如何操做这个函数,以至于拥有惰性求值、错误处理、异步调用等等很是牛掰的特性。
下面咱们看下函子的代码实现:
var Container = function (x) { this.__value = x; } // 函数式编程通常约定,函子有一个of方法 Container.of = x => new Container(x); // Container.of(‘abcd’); // 通常约定,函子的标志就是容器具备map方法。该方法将容器 // 里面的每个值, 映射到另外一个容器。 Container.prototype.map = function (f) { return Container.of(f(this.__value)) } Container.of(3) .map(x => x + 1) //=> Container(4) .map(x => 'Result is ' + x); //=> Container('Result is 4')
class Functor { constructor(val) { this.val = val; } map(f) { return new Functor(f(this.val)); } } (new Functor(2)).map(function (two) { return two + 2; }); // Functor(4)
Functor.of = function (val) { return new Functor(val); }; Functor.of(2).map(function (two) { return two + 2; }); // Functor(4)
下面咱们介绍一些经常使用的函子。
var Maybe = function (x) { this.__value = x; } Maybe.of = function (x) { return new Maybe(x); } Maybe.prototype.map = function (f) { return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value)); } Maybe.prototype.isNothing = function () { return (this.__value === null || this.__value === undefined); } //新的容器咱们称之为 Maybe(原型来自于Haskell,Haskell是通用函数式编程语言)
Functor.of(null).map(function (s) { return s.toUpperCase(); }); // TypeError Maybe.of(null).map(function (s) { return s.toUpperCase(); }); // Maybe(null)
咱们的容器能作的事情太少了,try/catch/throw 并非“纯”的,由于它从外部接管了咱们的函数,而且在这个函数出错时抛弃了它的返回值。Promise 是能够调用 catch 来集中处理错误的。事实上 Either 并不仅是用来作错误处理的,它表示了逻辑或。
条件运算if...else是最多见的运算之一,函数式编程里面,使用 Either 函子表达。Either 函子内部有两个值:左值(Left)和右值(Right)。右值是正常状况下使用的值,左值是右值不存在时使用的默认值。
class Either extends Functor { constructor(left, right) { this.left = left; this.right = right; } map(f) { return this.right ? Either.of(this.left, f(this.right)) : Either.of(f(this.left), this.right); } } Either.of = function (left, right) { return new Either(left, right); };
使用Either函子:
var addOne = function (x) { return x + 1; }; Either.of(5, 6).map(addOne); // Either(5, 7); Either.of(1, null).map(addOne); // Either(2, null); Either .of({ address: 'xxx' }, currentUser.address) .map(updateField);
class Ap extends Functor { ap(F) { return Ap.of(this.val(F.val)); } }
function addOne(x) { return x + 1; }
Ap.of(addOne).ap(Functor.of(1)) // ap函子,让addOne能够用后面函子中的val运算 结果为Ap(2)
class IO extends Monad { map(f) { return IO.of(compose(f, this.__value)) } }
在这里,咱们提到了Monad,Monad就是一种设计模式,表示将一个运算过程,经过函数拆解成互相链接的多个步骤。你只要提供下一步运算所需的函数,整个运算就会自动进行下去。Promise 就是一种 Monad。Monad 让咱们避开了嵌套地狱,能够轻松地进行深度嵌套的函数式编程,好比IO和其它异步任务。
class Monad extends Functor { join() { return this.val; } flatMap(f) { return this.map(f).join(); } }
关于更多的Monad介绍,能够移步知乎什么是Monad。
var clicks = Rx.Observable .fromEvent(document, 'click') .bufferCount(2) .subscribe(x => console.log(x)); // 打印出前2次点击事件
function main(sources) { const sinks = { DOM: sources.DOM.select('input').events('click') .map(ev => ev.target.checked) .startWith(false) .map(toggled => <div> <input type="checkbox" /> Toggle me <p>{toggled ? 'ON' : 'off'}</p> </div> ) }; return sinks; } const drivers = { DOM: makeDOMDriver('#app') }; run(main, drivers);
var abc = function (a, b, c) { return [a, b, c]; }; var curried = _.curry(abc); curried(1)(2)(3);
function square(n) {
return n * n;
}
var addSquare = _.flowRight(square, _.add); // 相似于上面说的函数组合
addSquare(1, 2);
// => 9
2.知乎 什么是 Monad (Functional Programming)?