美文网首页freeCodeCamp旅途
freeCodeCamp 旅途8 - 函数式编程

freeCodeCamp 旅途8 - 函数式编程

作者: HJSD | 来源:发表于2019-10-15 02:16 被阅读0次

    函数式编程

    函数式编程是一种基于函数计算的软件开发方法。像数学一样,函数在编程中通过输入产生输出。函数式编程遵循几个核心原则:

    • 独立于程序状态或全局变量,只依赖于传递给它们的参数进行计算
    • 限制更改程序状态,避免更改保存数据的全局对象
    • 对程序的副作用尽量小

    学习函数式编程

    函数式编程是一种解决方案简单,功能独立,对作用域外没有任何副作用的编程范式。INPUT -> PROCESS -> OUTPUT

    函数式编程:
    1)功能独立——不依赖于程序的状态(比如可能发生变化的全局变量);
    2)纯函数——同一个输入永远能得到同一个输出;
    3)有限的副作用——可以严格地限制函数外部对状态的更改导致的状态变化。

    const prepareTea = () => 'greenTea';
    const getTea = (numOfCups) => {
      const teaCups = [];  
      for(let cups = 1; cups <= numOfCups; cups += 1) {
        const teaCup = prepareTea();
        teaCups.push(teaCup);
      }
      return teaCups;
    };
    const tea4TeamFCC = getTea(40); 
    console.log(tea4TeamFCC);
    

    了解函数式编程术语

    Callbacks是被传递到另一个函数中调用的函数。例如在filter中,回调函数告诉 JavaScript 以什么规则过滤数组。
    函数就像其他正常值一样,可以赋值给变量、传递给另一个函数,或从其它函数返回,这种函数叫做first class函数。在 JavaScript 中,所有函数都是first class函数。
    将函数为参数或返回值的函数叫做higher order函数。
    当函数传递给另一个函数或从另一个函数返回时,那些传入或返回的函数可以叫做lambda

    const prepareGreenTea = () => 'greenTea';  //  绿茶准备过程
    const prepareBlackTea = () => 'blackTea';  //  红茶准备过程
    const getTea = (prepareTea, numOfCups) => {
      const teaCups = [];
      for(let cups = 1; cups <= numOfCups; cups += 1) {
        const teaCup = prepareTea();
        teaCups.push(teaCup);
      }
      return teaCups;
    };
    const tea4GreenTeamFCC = getTea(prepareGreenTea, 27); 
    const tea4BlackTeamFCC = getTea(prepareBlackTea, 13); 
    console.log(  tea4GreenTeamFCC,  tea4BlackTeamFCC);
    

    了解使用命令式编程的危害

    窗口对象由选项卡组成,通常会打开多个窗口。窗口对象中每个打开网站的标题都保存在一个数组中。在对浏览器进行了如打开新标签、合并窗口、关闭标签之类的操作后,你需要输出所有打开的标签。关掉的标签将从数组中删除,新打开的标签(为简单起见)则添加到数组的末尾。

    var Window = function(tabs) { 
      this.tabs = tabs; // 我们将数组记录在对象内部
    }; // tabs 是窗口中打开网站的标题数组
    Window.prototype.join = function (otherWindow) {
      this.tabs = this.tabs.concat(otherWindow.tabs);
      return this;
    }; // 当两个窗口合并成一个窗口时
    Window.prototype.tabOpen = function (tab) {
      this.tabs.push('new tab'); // 打开一个新的标签
      return this;
    };  // 在末尾打开一个新标签
    Window.prototype.tabClose = function (index) {
      var tabsBeforeIndex = this.tabs.splice(0, index); // 获取前面的标签
      var tabsAfterIndex = this.tabs.splice(index); // 获取后面的标签
      this.tabs = tabsBeforeIndex.concat(tabsAfterIndex); // 拼接到一起
      return this;
     }; // 关闭一个标签
    // 创建三个浏览器窗口
    var workWindow = new Window(['GMail', 'Inbox', 'Work mail', 'Docs', 'freeCodeCamp']); // 邮箱、文档及其他与工作相关的网站
    var socialWindow = new Window(['FB', 'Gitter', 'Reddit', 'Twitter', 'Medium']); // 社交网站
    var videoWindow = new Window(['Netflix', 'YouTube', 'Vimeo', 'Vine']); // 娱乐网站
    var finalTabs = socialWindow
                        .tabOpen() // 新开一个 cat memes 的标签
                        .join(videoWindow.tabClose(2)) // 在娱乐网站关闭第三个标签,加入数组
                        .join(workWindow.tabClose(1).tabOpen());
    alert(finalTabs.tabs); // 执行标签打开,关闭和其他操作
    

    使用函数式编程避免突变和副作用:问题出在tabClose()函数里的splice。splice修改了调用它的原始数组,所以第二次调用它时是基于修改后的数组,才给出了意料之外的结果。
    函数式编程的核心原则之一是不改变任何东西。变化会导致错误。如果一个函数不改变传入的参数、全局变量等数据,那么它造成问题的可能性就会小很多。
    在函数式编程中,改变或变更叫做mutation,这种改变的结果叫做“副作用”(side effect)。理想情况下,函数应该是不会产生任何副作用的pure function

    var fixedValue = 4;
    function incrementer () {
      return fixedValue + 1;
    }
    var newValue = incrementer(); // 应等于 5
    console.log(fixedValue); // 应打印 4
    

    传递参数以避免函数中的外部依赖

    函数式编程的另一个原则是:总是显式声明依赖关系。如果函数依赖于一个变量或对象,那么将该变量或对象作为参数直接传递到函数中。

    var fixedValue = 4;
    function incrementer (value) {
      return value + 1;
    }
    var newValue = incrementer(fixedValue); // 应等于 5
    console.log(fixedValue); // 应打印 4
    

    在函数中重构全局变量

    // 全局变量
    var bookList = ["The Hound of the Baskervilles", "On The Electrodynamics of Moving Bodies", "Philosophiæ Naturalis Principia Mathematica", "Disquisitiones Arithmeticae"];
    function add (arr, bookName) {
      let newArr = [...arr];  
      newArr.push(bookName);  
      return newArr; 
    }
    function remove (arr, bookName) {
      let newArr = [...arr]; 
      if (newArr.indexOf(bookName) >= 0) {  
        newArr.splice(newArr.indexOf(bookName), 1); 
        return newArr; // Return the new array.
        }
    }
    var newBookList = add(bookList, 'A Brief History of Time');
    var newerBookList = remove(bookList, 'On The Electrodynamics of Moving Bodies');
    var newestBookList = remove(add(bookList, 'A Brief History of Time'), 'On The Electrodynamics of Moving Bodies');
    console.log(bookList);
    //  等价于
    function add (list,bookName) {
      return [...list, bookName];
    }
    function remove (list,bookName) {
      if (list.indexOf(bookName) >= 0) {
        return list.filter((item) => item !== bookName);
        }
    }
    

    使用 map 方法从数组中提取数据

    函数在 JavaScript 中被视为First Class Objects,它们可以像任何其他对象一样使用。它们可以保存在变量中,存储在对象中,也可以作为函数参数传递。
    map方法是迭代数组中每一项的方式之一。在对每个元素应用回调函数后,它会创建一个新数组(不改变原来的数组)。

    rating = watchList.map( (item) => ({"title":item["Title"], "rating":item["imdbRating"]}) );
    

    在原型上实现 map 方法:map是一个纯函数,它的输出仅取决于输入的数组和作为参数传入的回调函数。纯函数可以改变其作用域内定义的局部变量,但我们最好不要这样做。

    var s = [23, 65, 98, 5];
    Array.prototype.myMap = function(callback){
      var newArray = [];
      for(let i = 0; i < this.length; i++){    newArray.push(callback(this[i]));  }
    //  等价于
    //  this.forEach(a => newArray.push(callback(a)));
      return newArray;
    };
    var new_s = s.myMap(function(item){
      return item * 2;
    });
    

    使用 filter 方法从数组中提取数据

    另一个有用的数组方法是filter()(即Array.prototype.filter())。filter方法会返回一个长度不大于原始数组的新数组。
    map一样,Filter不会改变原始数组,它接收一个回调函数,将回调内的逻辑应用于数组的每个元素。新数组包含根据回调函数内条件返回 true 的元素。

    var filteredList = watchList.map(function(e) {
      return {title: e["Title"], rating: e["imdbRating"]}
    }).filter((e) => e.rating >= 8);
    

    在原型上实现 filter 方法:

    var s = [23, 65, 98, 5];
    Array.prototype.myFilter = function(callback){
      var newArray = [];
      for (let i=0; i<this.length;i++){
        if(callback(this[i])=== true ){ newArray.push(this[i]);    }
      }
    //   等价于
    //    this.forEach(function(x) {    if (callback(x) == true) {  newArray.push(x);  }  });
      return newArray;
    };
    var new_s = s.myFilter(function(item){
      return item % 2 === 1;
    });
    

    使用 slice 方法返回数组的一部分

    slice方法可以从已有数组中返回指定元素。它接受两个参数,第一个规定从何处开始选取,第二个规定从何处结束选取(不包括该元素)。如果没有传参,则默认为从数组的开头开始到结尾结束,这是复制整个数组的简单方式。slice返回一个新数组,不会修改原始数组

    var arr = ["Cat", "Dog", "Tiger", "Zebra"];
    var newArray = arr.slice(1, 3);  // 将新数组设置为 ["Dog", "Tiger"]
    

    使用 slice 而不是 splice 从数组中移除元素:JavaScript 提供了splice方法,它接收两个参数:从哪里开始删除项目的索引,和要删除的项目数。如果没有提供第二个参数,默认情况下是移除到结尾的元素。但splice方法会改变调用它的原始数组。

    var cities = ["Chicago", "Delhi", "Islamabad", "London", "Berlin"];
    cities.splice(3, 1); // 返回 "London" 并将它从 cities 数组删除
    // cities 现在是 ["Chicago", "Delhi", "Islamabad", "Berlin"]
    

    使用 concat 方法组合两个数组

    Concatenation意思是将元素连接到尾部。同理,JavaScript 为字符串和数组提供了concat方法。对数组来说,在一个数组上调用concat方法,然后提供另一个数组作为参数添加到第一个数组末尾,返回一个新数组,不会改变任何一个原始数组。

    [1, 2, 3].concat([4, 5, 6]);   // 返回新数组 [1, 2, 3, 4, 5, 6]
    

    使用 concat 而不是 push 将元素添加到数组的末尾:函数式编程就是创建和使用 non-mutating 函数。

    var arr = [1, 2, 3];
    arr.push([4, 5, 6]); // arr 变成了 [1, 2, 3, [4, 5, 6]]  不是函数式编程
    

    使用 reduce 方法分析数据

    reduce()(即Array.prototype.reduce()),是 JavaScript 所有数组操作中最通用的方法。几乎可以用reduce方法解决所有数组处理问题。
    filtermap方法不支持对数组中两个不同元素的交互。举个例子,如果你想把数组中的元素拿来比较或者相加,用filtermap是做不到的。
    reduce方法允许更通用的数组处理方式,而且filtermap方法都可以当作是reduce的特殊实现。

    var averageRating = watchList.filter(x => x.Director === "Christopher Nolan").map(x => Number(x.imdbRating)).reduce((x1, x2) => x1 + x2) / watchList.filter(x => x.Director === "Christopher Nolan").length;
    

    使用 sort 方法按字母顺序给数组排序

    在alphabeticalOrder函数中使用sort方法对arr中的元素按照字母顺序排列:

    function alphabeticalOrder(arr) {
      return arr.sort(function (a, b) {
       if (a < b) return -1
       else if (a > b) return 1
       else return 0
      });
    }
    alphabeticalOrder(["a", "d", "c", "a", "z", "g"]);
    

    在不更改原始数组的前提下返回排序后的数组

    concat返回一个新数组,再用sort方法:

    var globalArray = [5, 6, 3, 2, 9];
    function nonMutatingSort(arr) {
      return [].concat(arr).sort(function(a,b){
        if(a<b) return -1
        else if(a>b) return 1
        else return 0
      });
    }
    nonMutatingSort(globalArray);
    

    使用 split 方法将字符串拆分成数组

    split方法用于把字符串分割成字符串数组,接收一个分隔符参数,分隔符可以是用于分解字符串或正则表达式的字符。

    function splitify(str) {
      return str.split(/\W/);
    }
    splitify("Hello World,I-am code");
    

    使用 join 方法将数组组合成字符串

    join方法用来把数组中的所有元素放入一个字符串,并通过指定的分隔符参数进行分隔。

    function sentensify(str) {
      return str.split(/\W/).join(" ");
    }
    sentensify("May-the-force-be-with-you");
    

    应用函数式编程将字符串转换为URL片段

    var globalTitle = "Winter Is Coming";
    function urlSlug(title) {
        return title.split(/\W/).filter((obj)=>{
            return obj !=='';
        }).join('-').toLowerCase();  
    }
    var winterComing = urlSlug(globalTitle); // 应为 "winter-is-coming"
    

    使用 every 方法检查数组中的每个元素是否符合条件

    使用every方法检查arr中是否所有元素都是正数:

    function checkPositive(arr) {
      return arr.every(function(a){
        return a > 0;
      });
    }
    checkPositive([1, 2, 3, -4, 5]);
    

    使用 some 方法检查数组中是否有元素是否符合条件

    使用some方法检查arr中是否所有元素都是正数:

    function checkPositive(arr) {
      return arr.some(function(a){
        return a > 0;
      });
    }
    checkPositive([1, 2, 3, -4, 5]);
    

    函数柯里化

    arity是函数所需的形参的数量。函数Currying意思是把接受多个arity的函数变换成接受单一arity的函数。换句话说,就是重构函数让它接收一个参数,然后返回接收下一个参数的函数,依此类推。

    function unCurried(x, y) {
      return x + y;
    }
    // 柯里化函数
    function curried(x) {
      return function(y) {
        return x + y;
      }
    }
    curried(1)(2) // 返回 3
    
    var funcForY = curried(1);
    console.log(funcForY(2)); // 打印 3
    
    function impartial(x, y, z) {
      return x + y + z;
    }
    var partialFn = impartial.bind(this, 1, 2);
    partialFn(10); // 返回 13
    
    function add(x) {
      return function(y){
        return function(z){
          return x + y + z;
        };
      };
    }
    add(10)(20)(30);
    

    相关文章

      网友评论

        本文标题:freeCodeCamp 旅途8 - 函数式编程

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