美文网首页
JavaScript函数式编程学习笔记

JavaScript函数式编程学习笔记

作者: xikoo0 | 来源:发表于2021-05-06 17:56 被阅读0次

    函数式编程

    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 中为了实现对象状态的不可变,往往会创建新的对象,因此,它对垃圾回收所产生的压力远远超过其他编程方式

    相关文章

      网友评论

          本文标题:JavaScript函数式编程学习笔记

          本文链接:https://www.haomeiwen.com/subject/vrbddltx.html