JavaScript函数式编程学习笔记

函数式编程

1. 什么是函数式编程

函数式编程(英语:functional programming)或称函数程序设计、泛函编程,是一种编程范式,它将计算机运算视为函数运算,并且避免使用程序状态以及易变对象。即对过程进行抽象,将数据以输入输出流的方式封装进过程内部,从而也降低系统的耦合度。

非函数式编程

myString="my name is String"
var words = [],
  count = 0;
text = myString.split(" ");
for (i = 0; count < 4, i < text.length; i++) {
  if (!text[i].match(/[0-9]/)) {
    words = words.concat(text[i]);
    count++;
  }
}
console.log(words); // ["my", "name", "is", "String"]

函数式编程

myString="my name is String"
var words = myString
  .split(" ")
  .filter(function(x) {
    return !x.match(/[1-9]+/);
  })
  .slice(0, 4);
console.log(words); // ["my", "name", "is", "String"]

2. 纯函数

“函数”是输入与输出之间的关系,即对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态

var messages = ["Hi", "Hello", "Sup", "Hey", "Hola"];

messages
  .map(function(s, i) {
    return printSomewhere(s, 100 * i * 10, 100 * i * 10);
  })
  .forEach(function(element) {
    document.body.appendChild(element);
  });

3. lambda函数又称匿名函数

如果函数只需要引用一次,则无需浪费函数名

匿名函数lambda:lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象。是指一类无需定义标识符(函数名)的函数或子程序。所谓匿名函数,通俗地说就是没有名字的函数,lambda函数没有名字,是一种简单的、在同一行中定义函数的方法

// 在JavaScript中
const lamFun = (x)=> {return x+1}

// 在JAVA中
() -> System.out.println("hello world");

// 在Python中
fun = lambda x, y: x * y
print(fun(2, 3))  // 6

4. abamdan柯里化

所谓"柯里化",就是把一个多参数的函数,转化为单参数函数。

// 柯里化之前
function add(x, y) {
  return x + y;
}

add(1, 2) // 3

// 柯里化之后
function addX(y) {
  return function (x) {
    return x + y;
  };
}

addX(2)(1)

5. 函数合成(compose)

如果一个值要经过多个函数,才能变成另外一个值,就可以把所有中间步骤合并成一个函数,这叫做"函数的合成"(compose)。
合成也是函数必须是纯的一个原因

var compose = function(f,g) {
  return function(x) {
    return f(g(x));
  };
};

var toUpperCase = function(x) { return x.toUpperCase(); };
var exclaim = function(x) { return x + '!'; };
var shout = compose(exclaim, toUpperCase);

shout("send in the clowns"); // "SEND IN THE CLOWNS!"

6. 函子、容器Functor

简单的例子

var Container = function(x) {
  this._value = x
}
Container.of = x => new Container(x)
Container.of(1)  
Container.prototype.map = function(f){
  return Container.of(f(this._value))
}
image.png
class Functor {
  constructor(val) { 
    this.val = val; 
  }

  map(f) {
    return new Functor(f(this.val));
  }
}

Functor是一个函子,它的map方法接受函数f作为参数,然后返回一个新的函子,里面包含的值是被f处理过的(f(this.val))
一般约定,函子的标志就是容器具有map方法。该方法将容器里面的每一个值,映射到另一个容器。
尾调用:指某个函数的最后一步是调用另一个函数

7. 自调用函数和闭包

闭包指的是有权访问父作用域的函数,即使在父函数关闭之后。

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

8. 递归

欧几里得算法,寻找两个数的最大公分母

function gcd(a, b) {
  if (b == 0) {
    // base case (conquer)
    return a;
  } else {
    // recursive case (divide)
    return gcd(b, a % b);
  }
}
console.log(gcd(12, 8));
console.log(gcd(100, 20));

9. 高阶函数

函数可以作为函数被传入,也称为回调函数,如函数合成运算。
可以返回函数作为输出,如函数柯里化运算。
高阶函数是一个接收函数作为参数或将函数作为输出返回的函数

add = (x) => (y) => x + y

add(10)  //(y) => x + y
add(10)(20) // 30

高阶函数的复杂实例

function powersOf(x) {
  return function(y) {
    // this is an anonymous function!
    return Math.pow(x, y);
  };
}
powerOfTwo = powersOf(2);
console.log(powerOfTwo(1)); // 2
console.log(powerOfTwo(2));

缺点:在堆栈中难以识别,不方便调试

荣誉函数

map() filter() reduce() 这三个函数为函数式编程带来便利

实现一个类似Map的高阶函数

function mapForEach(arr,fn){
  const newArray = []
  for(let i=0;i<arr.length;i++){
    newArray.push(fn(arr[i]))
  }
  return [...newArray]
}
const arrs=['May','Marry','Jacken','Yumy','Sandy']
const arrMap = mapForEach(arrs,(item)=>{ return item.length })
console.log(arrMap) //[3, 5, 6, 4, 5]

10. 学习完自己的代码优化实例

多维数组遍历

let arr4 = [{name:'one',children:[{name:'two',children:[{name:'three'}]}]}]

function renderList(arrs){
  const list = []
  function renderArr(arr=[]){
   arr.forEach(item=>{
     list.push({name:item.name})
     if(item.children && item.children.length){
       renderArr(item.children)
     }
   })
  }
 renderArr(arrs)
 return list
}
console.log('lists',renderList(arr4))
// [0: {name: "one"},1: {name: "two"},2: {name: "three"}]

源数据结构如下:


image.png
const data = [{
    id: 5,
    label: "工业",
    children:[
        {   id: 10012,
            label: "塑料加工",
            children:[
                {id: 10000129,label: "共混料"},
                {id: 10000130,label: "母料(色母等)"},
                {id: 10000301,label: "木塑复合材料"}
            ]
        }
    ]
}]

未优化前

renderTreeList ({ list = [], isOpen = false, rank = 0, parentId = [] }) {
      list.forEach(item => {
        this.treeList.push({
          id: item.id,
          label: item.label,
          rank,
          parentId,
          isOpen: false,
          show: rank === 0,
          checked: this.selectedIds.includes(item.id)
        })
        if (Array.isArray(item.children) && item.children.length > 0) {
          let parents = [...parentId]
          parents.push(item.id)
          this.renderTreeList({ list: item.children, rank: rank + 1, parentId: parents })
        } else {
          this.treeList[this.treeList.length - 1].lastRank = true
        }
      })
      this.treeList = [...this.isDefaultOpen({ arr: this.treeList, selectedIds: this.selectedIds, isOpen })]
    }
    

优化后

 renderList (arrs) {
      const arrList = []
      const renderArr = function ({ list = [], isOpen = false, rank = 0, parentId = [] }) {
        list.forEach(item => {
          arrList.push({
            id: item.id,
            label: item.label,
            rank,
            parentId,
            isOpen: false,
            show: rank === 0,
            checked: true
          })
          if (Array.isArray(item.children) && item.children.length > 0) {
            let parents = [...parentId]
            parents.push(item.id)
            renderArr({ list: item.children, rank: rank + 1, parentId: parents })
          } else {
            arrList[arrList.length - 1].lastRank = true
          }
        })
      }
      renderArr({ list: arrs })
      return arrList
    }
    

es6数组拉平方法

https://es6.ruanyifeng.com/?search=%E4%B8%80%E7%BB%B4&x=0&y=0#docs/array#%E6%95%B0%E7%BB%84%E5%AE%9E%E4%BE%8B%E7%9A%84-flat%EF%BC%8CflatMap

总结:

函数式编程的好处:

1.不容易产生bug,方便测试和并行处理;
2.可以抛弃this,避免被this指向弄晕;
3.打包过程中可以更好的利用 tree shaking 过滤无用代码;
4.有很多库可以帮助我们进行函数式开发,比如经典的lodash。

缺点:

1.存在性能问题,Map、filter这些需要遍历多次,增加时间开销
2.资源占用,在 JS 中为了实现对象状态的不可变,往往会创建新的对象,因此,它对垃圾回收所产生的压力远远超过其他编程方式

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354

推荐阅读更多精彩内容