<font id="zqva1"></font>
<rt id="zqva1"></rt>
  • <tt id="zqva1"></tt>
    <cite id="zqva1"></cite>

    <cite id="zqva1"><noscript id="zqva1"></noscript></cite>
      <rp id="zqva1"><meter id="zqva1"></meter></rp>

        <cite id="zqva1"></cite>
          <b id="zqva1"></b>
          <rp id="zqva1"></rp>
          <cite id="zqva1"></cite>

          <rt id="zqva1"></rt>

        1. <rp id="zqva1"></rp>

          JavaScript 函數式編程

          時間:?2017-10-27閱讀:?799標簽:?函數作者:?化辰

          JavaScript 函數式編程是一個存在了很久的話題,但似乎從 2016 年開始,它變得越來越火熱。這可能是因為 ES6 語法對于函數式編程更為友好,也可能是因為諸如 RxJS (ReactiveX) 等函數式框架的流行。

          看過許多關于函數式編程的講解,但是其中大部分是停留在理論層面,還有一些是僅針對 Haskell 等純函數式編程語言的。而本文旨在聊一聊我眼中的函數式編程在 JavaScript 中的具體實踐,之所以是 “我眼中的” 即我所說的僅代表個人觀點,可能和部分 嚴格概念 是有沖突的。

          本文將略去一大堆形式化的概念介紹,重點展示在 JavaScript 中到底什么是函數式的代碼、函數式代碼與一般寫法有什么區別、函數式的代碼能給我們帶來什么好處以及常見的一些函數式模型都有哪些。


          我理解的函數式編程

          我認為函數式編程可以理解為,以函數作為主要載體的編程方式,用函數去拆解、抽象一般的表達式

          與命令式相比,這樣做的好處在哪?主要有以下幾點:

          • 語義更加清晰
          • 可復用性更高
          • 可維護性更好
          • 作用域局限,副作用少

          基本的函數式編程

          下面例子是一個具體的函數式體現

          // 數組中每個單詞,首字母大寫
          
          
          // 一般寫法
          const arr = ['apple', 'pen', 'apple-pen'];
          for(const i in arr){
            const c = arr[i][0];
            arr[i] = c.toUpperCase() + arr[i].slice(1);
          }
          
          console.log(arr);
          
          
          // 函數式寫法一
          function upperFirst(word) {
            return word[0].toUpperCase() + word.slice(1);
          }
          
          function wordToUpperCase(arr) {
            return arr.map(upperFirst);
          }
          
          console.log(wordToUpperCase(['apple', 'pen', 'apple-pen']));
          
          
          // 函數式寫法二
          console.log(arr.map(['apple', 'pen', 'apple-pen'], word => word[0].toUpperCase() + word.slice(1)));

          當情況變得更加復雜時,表達式的寫法會遇到幾個問題:

          1. 表意不明顯,逐漸變得難以維護
          2. 復用性差,會產生更多的代碼量
          3. 會產生很多中間變量

          函數式編程很好的解決了上述問題。首先參看 函數式寫法一,它利用了函數封裝性將功能做拆解(粒度不唯一),并封裝為不同的函數,而再利用組合的調用達到目的。這樣做使得表意清晰,易于維護、復用以及擴展。其次利用 高階函數,Array.map 代替 for…of 做數組遍歷,減少了中間變量和操作。

          而 函數式寫法一 和 函數式寫法二 之間的主要差別在于,可以考慮函數是否后續有復用的可能,如果沒有,則后者更優。


          鏈式優化

          從上面 函數式寫法二 中我們可以看出,函數式代碼在寫的過程中,很容易造成 橫向延展,即產生多層嵌套,下面我們舉個比較極端點的例子。

          // 計算數字之和
          
          
          // 一般寫法
          console.log(1 + 2 + 3 - 4)
          
          
          // 函數式寫法
          function sum(a, b) {
            return a + b;
          }
          
          function sub(a, b) {
            return a - b;
          }
          
          console.log(sub(sum(sum(1, 2), 3), 4);

          本例僅為展示 橫向延展 的比較極端的情況,隨著函數的嵌套層數不斷增多,導致代碼的可讀性大幅下降,還很容易產生錯誤。

          在這種情況下,我們可以考慮多種優化方式,比如下面的 鏈式優化 。

          // 優化寫法 (嗯,你沒看錯,這就是 lodash 的鏈式寫法)
          const utils = {
            chain(a) {
              this._temp = a;
              return this;
            },
            sum(b) {
              this._temp += b;
              return this;
            },
            sub(b) {
              this._temp -= b;
              return this;
            },
            value() {
              const _temp = this._temp;
              this._temp = undefined;
              return _temp;
            }
          };
          
          console.log(utils.chain(1).sum(2).sum(3).sub(4).value());

          這樣改寫后,結構會整體變得比較清晰,而且鏈的每一環在做什么也可以很容易的展現出來。函數的嵌套和鏈式的對比還有一個很好的例子,那就是 回調函數 和 Promise 模式

          // 順序請求兩個接口
          
          
          // 回調函數
          import $ from 'jquery';
          $.post('a/url/to/target', (rs) => {
            if(rs){
              $.post('a/url/to/another/target', (rs2) => {
                if(rs2){
                  $.post('a/url/to/third/target');
                }
              });
            }
          });
          
          
          // Promise
          import request from 'catta';  // catta 是一個輕量級請求工具,支持 fetch,jsonp,ajax,無依賴
          request('a/url/to/target')
            .then(rs => rs ? $.post('a/url/to/another/target') : Promise.reject())
            .then(rs2 => rs2 ? $.post('a/url/to/third/target') : Promise.reject());

          隨著回調函數嵌套層級和單層復雜度增加,它將會變得臃腫且難以維護,而 Promise 的鏈式結構,在高復雜度時,仍能縱向擴展,而且層次隔離很清晰。


          常見的函數式編程模型

          閉包(Closure)

          可以保留局部變量不被釋放的代碼塊,被稱為一個閉包

          閉包的概念比較抽象,相信大家都或多或少知道、用到這個特性

          那么閉包到底能給我們帶來什么好處?

          先來看一下如何創建一個閉包:

          // 創建一個閉包
          function makeCounter() {
            let k = 0;
          
            return function() {
              return ++k;
            };
          }
          
          const counter = makeCounter();
          
          console.log(counter());  // 1
          console.log(counter());  // 2


          makeCounter 這個函數的代碼塊,在返回的函數中,對局部變量 k ,進行了引用,導致局部變量無法在函數執行結束后,被系統回收掉,從而產生了閉包。而這個閉包的作用就是,“保留住“ 了局部變量,使內層函數調用時,可以重復使用該變量;而不同于全局變量,該變量只能在函數內部被引用。

          換句話說,閉包其實就是創造出了一些函數私有的 ”持久化變量“。

          所以從這個例子,我們可以總結出,閉包的創造條件是:

          1. 存在內、外兩層函數
          2. 內層函數對外層函數的局部變量進行了引用

          閉包的用途

          閉包的主要用途就是可以定義一些作用域局限的持久化變量,這些變量可以用來做緩存或者計算的中間量等等。

          // 簡單的緩存工具
          // 匿名函數創造了一個閉包
          const cache = (function() {
            const store = {};
            
            return {
              get(key) {
                return store[key];
              },
              set(key, val) {
                store[key] = val;
              }
            }
          }());
          
          cache.set('a', 1);
          cache.get('a');  // 1

          上面例子是一個簡單的緩存工具的實現,匿名函數創造了一個閉包,使得 store 對象 ,一直可以被引用,不會被回收。

          閉包的弊端

          持久化變量不會被正常釋放,持續占用內存空間,很容易造成內存浪費,所以一般需要一些額外手動的清理機制。


          高階函數

          接受或者返回一個函數的函數稱為高階函數

          聽上去很高冷的一個詞匯,但是其實我們經常用到,只是原來不知道他們的名字而已。JavaScript 語言是原生支持高階函數的,因為 JavaScript 的函數是一等公民,它既可以作為參數又可以作為另一個函數的返回值使用。

          我們經常可以在 JavaScript 中見到許多原生的高階函數,例如 Array.map , Array.reduce , Array.filter

          下面以 map 為例,我們看看他是如何使用的

          map (映射)

          映射是對集合而言的,即把集合的每一項都做相同的變換,產生一個新的集合

          map作為一個高階函數,他接受一個函數參數作為映射的邏輯

          // 數組中每一項加一,組成一個新數組
          
          
          // 一般寫法
          const arr = [1,2,3];
          const rs = [];
          for(const n of arr){
            rs.push(++n);
          }
          console.log(rs)
          
          
          // map改寫
          const arr = [1,2,3];
          const rs = arr.map(n => ++n);

          上面一般寫法,利用 for...of 循環的方式遍歷數組會產生額外的操作,而且有改變原數組的風險

          而 map 函數封裝了必要的操作,使我們僅需要關心映射邏輯的函數實現即可,減少了代碼量,也降低了副作用產生的風險。


          柯里化(Currying)

          給定一個函數的部分參數,生成一個接受其他參數的新函數

          可能不常聽到這個名詞,但是用過 undescore 或 lodash 的人都見過他。

          有一個神奇的 _.partial 函數,它就是柯里化的實現

          // 獲取目標文件對基礎路徑的相對路徑
          
          
          // 一般寫法
          const BASE = '/path/to/base';
          const relativePath = path.relative(BASE, '/some/path');
          
          
          // _.parical 改寫
          const BASE = '/path/to/base';
          const relativeFromBase = _.partial(path.relative, BASE);
          
          const relativePath = relativeFromBase('/some/path');

          通過 _.partial ,我們得到了新的函數 relativeFromBase ,這個函數在調用時就相當于調用 path.relative ,并默認將第一個參數傳入 BASE ,后續傳入的參數順序后置。

          本例中,我們真正想完成的操作是每次獲得相對于 BASE 的路徑,而非相對于任何路徑。柯里化可以使我們只關心函數的部分參數,使函數的用途更加清晰,調用更加簡單。


          組合(Composing)

          將多個函數的能力合并,創造一個新的函數

          同樣你第一次見到他可能還是在 lodash 中,compose 方法(現在叫 flow)

          // 數組中每個單詞大寫,做 Base64
          
          
          // 一般寫法 (其中一種)
          const arr = ['pen', 'apple', 'applypen'];
          const rs = [];
          for(const w of arr){
            rs.push(btoa(w.toUpperCase()));
          }
          console.log(rs);
          
          
          // _.flow 改寫
          const arr = ['pen', 'apple', 'applypen'];
          const upperAndBase64 = _.partialRight(_.map, _.flow(_.upperCase, btoa));
          console.log(upperAndBase64(arr));

          _.flow 將轉大寫和轉 Base64 的函數的能力合并,生成一個新的函數。方便作為參數函數或后續復用。


          自己的觀點

          我理解的 JavaScript 函數式編程,可能和許多傳統概念不同。我并不只認為 高階函數 算函數式編程,其他的諸如普通函數結合調用、鏈式結構等,我都認為屬于函數式編程的范疇,只要他們是以函數作為主要載體的。

          而我認為函數式編程并不是必須的,它也不應該是一個強制的規定或要求。與面向對象或其他思想一樣,它也是其中一種方式。我們更多情況下,應該是幾者的結合,而不是局限于概念。

          本文轉載于淘寶前端團隊化辰的文章


          站長推薦

          1.阿里云: 本站目前使用的是阿里云主機,安全/可靠/穩定。點擊領取2000元代金券、了解最新阿里云產品的各種優惠活動點擊進入

          2.騰訊云: 提供云服務器、云數據庫、云存儲、視頻與CDN、域名等服務。騰訊云各類產品的最新活動,優惠券領取點擊進入

          3.廣告聯盟: 整理了目前主流的廣告聯盟平臺,如果你有流量,可以作為參考選擇適合你的平臺點擊進入

          鏈接: http://www.modern-decoration.com.cn/article/detial/22

          理解與使用JavaScript中的回調函數

          在JavaScript中,函數是第一類對象,這意味著函數可以像對象一樣按照第一類管理被使用。既然函數實際上是對象:它們能被“存儲”在變量中,能作為函數參數被傳遞,能在函數中被創建,能從函數中返回。

          10個非常實用的Js工具函數

          生成一周時間new Array 創建的數組只是添加了length屬性,并沒有實際的內容。通過擴展后,變為可用數組用于循環,類型判斷判斷核心使用Object.prototype.toString,這種方式可以準確的判斷數據類型。

          Javascript之尾調用

          尾調用是函數式編程的一個重要的概念,本篇文章就來學習下尾調用相關的知識。有說過在一個函數中輸出一個函數,則這個函數可以被成為高階函數。本文的主角尾調用和它類似,如果一個函數返回的是另一個函數的調用結果,那么就被稱為尾調用。

          javascript中的匿名方法(函數)是什么?

          方法(method)是通過對象調用的javascript函數。也就是說,方法也是函數,只是比較特殊的函數。JavaScript中的匿名方法即匿名函數是沒有函數名稱的函數。

          JavaScript中怎么調用函數?

          JavaScript怎么調用函數?其實在JavaScript中函數有4種調用方式。下面本篇文章就來給大家介紹一下JavaScript函數的4種調用方式,希望對大家有所幫助。

          CSS的var()函數怎么用?

          CSS中的var()函數可用于插入自定義屬性(有時稱為“css變量”)的值,而不是插入其他屬性值的任何部分。隨著sass,less預編譯的流行,css也隨即推出了變量定義var函數。var()函數,就如同sass和less等預編譯軟件一樣,可以定義變量并且進行對應的使用。

          如何實現 lodash.get 函數及可選鏈操作簡化取值

          lodash 基本上成為了 js 項目的標配工具函數,廣泛應用在各種服務端以及前端應用中,但是它的包體積略大了一些。對于服務端來說,包的體積并不是十分的重要,或者換句話說,不像前端那樣對包的體積特別敏感,一分一毫都會影響頁面打開的性能,從而影響用戶體驗。

          JavaScript高階函數

          Javascript中的函數本質上都指向某個變量,既然變量可以指向函數,函數的參數可以接受變量,那么函數是不是可以可以作為另一個函數的入參?因為Javascript是一門弱類型語言,不會對函數輸入值和函數的輸出值進行強定義和類型檢查

          javascript函數記憶

          記憶的定義基本上描述了實施該技術的有用方案。 當您擁有一個昂貴的函數時,如果給定相同的參數,該函數將始終導致相同的值,則緩存結果并在下次調用它時返回緩存的值非常有效。 這樣,寶貴的時間不會浪費在重新計算值上

          Js中函數的5個高級技巧

          函數是由事件驅動的或者當它被調用時執行的可重復使用的代碼塊。函數對任何一門語言來說都是一個核心的概念,在javascript中更是如此。本文將深入介紹函數的5個高級技巧。

          內容以共享、參考、研究為目的,不存在任何商業目的。其版權屬原作者所有,如有侵權或違規,請與小編聯系!情況屬實本人將予以刪除!

          文章投稿關于web前端網站點搜索站長推薦網站地圖站長QQ:522607023

          小程序專欄: 土味情話心理測試腦筋急轉彎幽默笑話段子句子語錄成語大全運營推廣

          国产精品高清视频免费 - 视频 - 在线观看 - 影视资讯 - 唯爱网