<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>

          Js函數式編程,給你的代碼增加一點點函數式編程的特性

          時間:?2017-11-07閱讀:?814標簽:?函數作者:?Peeke Kuepers

          -- 給你的代碼增加一點點函數式編程的特性

          最近我對函數式編程非常感興趣。這個概念讓我著迷:應用數學來增強抽象性和強制純粹性,以避免副作用,并實現代碼的良好可復用性。同時,函數式編程非常復雜。

          函數式編程有一個非常陡峭的學習曲線,因為它來源于數學中的范疇論。接觸不久之后,就將遇到諸如組合(composition)、恒等(identity),函子(functor)、單子(monad),以及逆變(contravariant)等術語。我根本不太了解這些概念,可能這也是我從來沒有在實踐中運用函數式編程的原因。

          我開始思考:在常規的命令式編程和完全的函數式編程之間是否可能會有一些中間形式?既允許在代碼庫引入函數式編程的一些很好的特性,同時暫時保留已有的舊代碼。

          對我而言,函數式編程最大的作用就是強制你編寫聲明性代碼:代碼描述你做什么,而不是在描述如何做。這樣就可以輕松了解特定代碼塊的功能,而無需了解其真正的運行原理。事實證明,編寫聲明式代碼是函數式編程中最簡單的部分之一。

          循環

          ...一個循環就是一個命令式控制結構,難以重用,并且難以插入到其他操作中。此外,它還得不斷變化代碼來響應新的迭代需求。
          -- Luis Atencio

          所以,讓我們先看一下循環,循環是命令式編程的一個很好的例子。循環涉及很多語法,都是描述它們的行為是如何工作,而不是它們在做什么。例如,看看這段代碼:

          function helloworld(arr) {
              for (let i = 1; i < arr.length; i++) {
                  arr[i] *= 2
                  if (arr[i] % 2 === 0) {
                      doSomething(arr[i])
                  }
              }
          }
          

          這段代碼在做什么呢?它將數組內除第一個數字 (let i = 1)的其他所有數字乘以 2,如果是偶數的話(if (arr % 2 === 0)),就進行某些操作。在此過程中,原始數組的值會被改變。但這通常是不必要的,因為數組可能還會在代碼庫中的其他地方用到,所以剛才所做的改變可能會導致意外的結果。

          但最主要的原因是,這段代碼看起來很難一目了然。它是命令式的,for 循環告訴我們如何遍歷數組,在里面,使用一個 if 語句有條件地調用一個函數。

          我們可以通過使用數組方法以聲明式的方式重寫這段代碼。數組方法直接表達所做的事,比較常見的方法包括:forEach,map,filter,reduce 和 slice。

          結果就像下面這樣:

          function helloworld(arr) {
              const evenNumbers = n => n % 2 === 0
          
              arr
                  .slice(1)
                  .map(v => v * 2)
                  .filter(evenNumbers)
                  .forEach(v => doSomething(v))    
          }
          

          在這個例子中,我們使用一種很好的,扁平的鏈式結構去描述我們在做什么,明確表明意圖。此外,我們避免了改變原始數組,從而避免不必要的副作用,因為大多數數組方法會返回一個新數組。當箭頭函數開始變得越來越復雜時,可以地將其提取到一個特定的函數中,比如 evenNumbers,?從而盡量保持結構簡單易讀。

          在上面的例子,鏈式調用并沒有返回值,而是以 forEach 結束。然而,我們可以輕松地剝離最后一部分,并返回結果,以便我們可以在其他地方處理它。如果還需要返回除數組以外的任何東西,可以使用 reduce 函數。

          對于接下來的一個例子,假設我們有一組 JSON 數據,其中包含在一個虛構歌唱比賽中不同國家獲得的積分:

          [
              {
                  "country": "NL",
                  "points": 12
              },
              {
                  "country": "BE",
                  "points": 3
              },
              {
                  "country": "NL",
                  "points": 0
              },
              ...
          ]
          

          我們想計算荷蘭(NL)獲得的總積分,根據印象中其強大的音樂能力,我們可以認為這是一個非常高的分數,但我們想要更精確地確認這一點。

          使用循環可能會是這樣:

          function countVotes(votes) {
              let score = 0;
          
              for (let i = 0; i < votes.length; i++) {
                  if (votes[i].country === 'NL') {
                      score += votes[i].points;
                  }
              }
          
              return score;
          }
          

          使用數組方法重構,我們得到一個更干凈的代碼片段:

          function countVotes(votes) {
              const sum = (a, b) => a + b;
          
              return votes
                  .filter(vote => vote.country === 'NL')
                  .map(vote => vote.points)
                  .reduce(sum);
          }
          

          有時候 reduce 可能有點難以閱讀,將 reduce 函數提取出來會在理解上有幫助。在上面的代碼片段中,我們定義了一個 sum 函數來描述函數的作用,因此方法鏈仍然保持很好的可讀性。

          if else 語句

          接下來,我們來聊聊大家都很喜歡的 if else 語句,if else 語句也是命令式代碼里一個很好的例子。為了使我們的代碼更具聲明式,我們將使用三元表達式。

          一個三元表達式是 if else 語句的替代語法。以下兩個代碼塊具有相同的效果:

          // Block 1
          if (condition) {
              doThis();
          } else {
              doThat();
          }
          
          // Block 2
          const value = condition ? doThis() : doThat();
          

          當在定義(或返回)一個常量時,三元表達式非常有用。使用 if else 語句會將該變量的使用范圍限制在語句內,通過使用三元語句,我們可以避免這個問題:

          if (condition) {
              const a = 'foo';
          } else {
              const a = 'bar';
          }
          
          const b = condition ? 'foo' : 'bar';
          
          console.log(a); // Uncaught ReferenceError: a is not defined
          console.log(b); // 'bar'
          

          現在,我們來看看如何應用這一點來重構一些更重要的代碼:

          const box = element.getBoundingClientRect();
          
          if (box.top - document.body.scrollTop > 0 && box.bottom - document.body.scrollTop < window.innerHeight) {
              reveal();
          } else {
              hide();
          }
          

          那么,上面的代碼發生了什么呢?if 語句檢查元素當前是否在頁面的可見部分內,這個信息在代碼的任何地方都沒有表達出來。基于此布爾值,再調用 reveal() 或者 hide() 函數。

          將這個 if 語句轉換成三元表達式迫使我們將條件移動到它自己的變量中。這樣我們可以將三元表達式組合在一行上,現在通過變量的名稱來傳達布爾值表示的內容,這樣還不錯。

          const box = element.getBoundingClientRect();
          const isInViewport = 
              box.top - document.body.scrollTop > 0 && 
              box.bottom - document.body.scrollTop < window.innerHeight;
          
          isInViewport ? reveal() : hide();
          

          通過這個例子,重構帶來的好處可能看起來不大。接下來會有一個相比更復雜的例子:

          elements
              .forEach(element => {
                  const box = element.getBoundingClientRect();
          
                  if (box.top - document.body.scrollTop > 0 && box.bottom - document.body.scrollTop < window.innerHeight) {
                      reveal();
                  } else {
                      hide();
                  }
          
              });
          

          這很不好,打破了我們優雅的扁平的調用鏈,從而使代碼更難讀。我們再次使用三元操作符,而在使用它的時候,使用 isInViewport 檢查,并跟它自己的動態函數分開。

          const isInViewport = element => {
              const box = element.getBoundingClientRect();
              const topInViewport = box.top - document.body.scrollTop > 0;
              const bottomInViewport = box.bottom - document.body.scrollTop < window.innerHeight;
              return topInViewport && bottomInViewport;
          };
          
          elements
              .forEach(elem => isInViewport(elem) ? reveal() : hide());
          

          此外,現在我們將 isInViewport 移動到一個獨立函數,可以很容易地把它放在它自己的 helper 類/對象之內:

          import { isInViewport } from 'helpers';
          
          elements
              .forEach(elem => isInViewport(elem) ? reveal() : hide());
          

          雖然上面的例子依賴于所處理的是數組,但是在不明確是在數組的情況下,也可以采用這種編碼風格。

          例如,看看下面的函數,它通過三條規則來驗證密碼的有效性。

          import { passwordRegex as requiredChars } from 'regexes'
          import { getJson } from 'helpers'
          
          const validatePassword = async value => {
            if (value.length < 6) return false
            if (!requiredChars.test(value)) return false
          
            const forbidden = await getJson('/forbidden-passwords')
            if (forbidden.includes(value)) return false
          
            return value
          }
          
          validatePassword(someValue).then(persist)
          

          如果我們使用數組包裝初始值,就可以使用在上面的例子中里面所用到的所有數組方法。此外,我們已經將驗證函數打包成 validationRules 使其可重用。

          import { minLength, matchesRegex, notBlacklisted } from 'validationRules'
          import { passwordRegex as requiredChars } from 'regexes'
          import { getJson } from 'helpers'
          
          const validatePassword = async value => {
            const result = Array.from(value)
              .filter(minLength(6))
              .filter(matchesRegex(requiredChars))
              .filter(await notBlacklisted('/forbidden-passwords'))
              .shift()
          
            if (result) return result
            throw new Error('something went wrong...')
          }
          
          validatePassword(someValue).then(persist)
          

          目前在 JavaScript 中有一個 管道操作符 的提案。使用這個操作符,就不用再把原始值換成數組了。可以直接在前面的值調用管道操作符之后的函數,有點像 Array 的 map 功能。修改之后的代碼大概就像這樣:

          import { minLength, matchesRegex, notBlacklisted } from 'validationRules'
          import { passwordRegex as requiredChars } from 'regexes'
          import { getJson } from 'helpers'
          
          const validatePassword = async value =>
            value
              |> minLength(6)
              |> matchesRegex(requiredChars)
              |> await notBlacklisted('/forbidden-passwords')
          
          try { someValue |> await validatePassword |> persist }
          catch(e) {
            // handle specific error, thrown in validation rule
          }
          

          但需要注意的是,這仍然是一個非常早期的提案,不過可以稍微期待一下。

          事件

          最后,我們來看看事件處理。一直以來,事件處理很難以扁平化的方式編寫代碼。可以 Promise 化來保持一種鏈式的,扁平化的編程風格,但 Promise 只能 resolve 一次,而事件絕對會多次觸發。

          在下面的示例中,我們創建一個類,它對用戶的每個輸入值進行檢索,結果是一個自動補全的數組。首先檢查字符串是否長于給定的閾值長度。如果滿足條件,將從服務器檢索自動補全的結果,并將其渲染成一系列標簽。

          注意代碼的不“純”,頻繁地使用 this 關鍵字。幾乎每個函數都在訪問 this 這個關鍵字:

          譯注:作者在這里使用 "this keyword",有一種雙關的意味
          import { apiCall } from 'helpers'
          
          class AutoComplete {
          
            constructor (options) {
          
              this._endpoint = options.endpoint
              this._threshold = options.threshold
              this._inputElement = options.inputElement
              this._containerElement = options.list
          
              this._inputElement.addEventListener('input', () =>
                this._onInput())
          
            }
          
            _onInput () {
          
              const value = this._inputElement.value
          
              if (value > this._options.threshold) {
                this._updateList(value)
              }
          
            }
          
            _updateList (value) {
          
              apiCall(this._endpoint, { value })
                .then(items => this._render(items))
                .then(html => this._containerElement = html)
          
            }
          
            _render (items) {
          
              let html = ''
          
              items.forEach(item => {
                html += `<a href="${ item.href }">${ item.label }</a>`
              })
          
              return html
          
            }
          
          }
          

          通過使用 Observable,我們將用一種更好的方式對這段代碼進行重寫。可以簡單將 Observable 理解成一個能夠多次 resolve 的 Promise。

          Observable 類型可用于基于推送模型的數據源,如 DOM 事件,定時器和套接字

          Observable 提案目前處于 Stage-1。在下面 listen 函數的實現是從 GitHub 上的提案中直接復制的,主要是將事件監聽器轉換成 Observable。可以看到,我們可以將整個 AutoComplete 類重寫為單個方法的函數鏈。

          import { apiCall, listen } from 'helpers';
          import { renderItems } from 'templates'; 
          
          function AutoComplete ({ endpoint, threshold, input, container }) {
          
            listen(input, 'input')
              .map(e => e.target.value)
              .filter(value => value.length >= threshold)
              .forEach(value => apiCall(endpoint, { value }))
              .then(items => renderItems(items))
              .then(html => container.innerHTML = html)
          
          }
          

          由于大多數 Observable 庫的實現過于龐大,我很期待 ES 原生的實現。map,filter和 forEach方法還不是規范的一部分,但是在 zen-observable 已經在擴展 API 實現,而 zen-observable 本身是 ES Observables 的一種實現 。

          --

          我希望你會對這些“扁平化”模式感興趣。就個人而言,我很喜歡以這種方式重寫我的程序。你接觸到的每一段代碼都可以更易讀。使用這種技術獲得的經驗越多,就越來越能認識到這一點。記住這個簡單的法則:

          The flatter the better!


          原文:Writing flat & declarative code
          作者:Peeke Kuepers
          站長推薦

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

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

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

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

          理解與使用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

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

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