亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

無循環 JavaScript(map、reduce、filter和find)

2019-11-19 16:52:56
字體:
來源:轉載
供稿:網友

之前有討論過,縮進(非常粗魯地)增加了代碼復雜性。我們的目標是寫出復雜度低的 JavaScript 代碼。通過選擇一種合適的抽象來解決這個問題,可是你怎么能知道選擇哪一種抽象呢?很遺憾的是到目前為止,沒有找到一個具體的例子能回答這個問題。這篇文章中我們討論不用任何循環如何處理 JavaScript 數組,最終得出的效果是可以降低代碼復雜性。

循環是一種很重要的控制結構,它很難被重用,也很難插入到其他操作之中。另外,它意味著隨著每次迭代,代碼也在不斷的變化之中。――Luis Atencio

我們先前說過,像循環這樣的控制結構引入了復雜性。但是也沒有給出確切的證據證明這一點,我們先看看 JavaScript 中循環的工作原理。

循環

在 JavaScript 中,至少有四、五種實現循環的方法,最基礎的是 while 循環。我們首先先創建一個示例函數和數組:

// oodlify :: String -> Stringfunction oodlify(s) { return s.replace(/[aeiou]/g, 'oodle');}const input = [ 'John', 'Paul', 'George', 'Ringo',];

現在有了一個數組,我們想要用 oodlify 函數處理每一個元素。如果用 while 循環,就類似于這樣:

let i = 0;const len = input.length;let output = [];while (i < len) { let item = input[i]; let newItem = oodlify(item); output.push(newItem); i = i + 1;}

注意這里發生的事情,我們用了一個初始值為 0 的計數器 i,每次循環都會自增。而且每次循環中都和 len 進行比較以保證循環特定次數以后終止循環。這種利用計數器進行循環控制的模式太常用了,所以 JavaScript 提供了一種更加簡潔的寫法: for 循環,寫起來如下:

const len = input.length;let output = [];for (let i = 0; i < len; i = i + 1) { let item = input[i]; let newItem = oodlify(item); output.push(newItem);}

這一結構非常有用,while循環非常容易把自增的 i 給忘掉,進而引起無限循環;而for循環把和計數器相關的代碼都放到了上面,這樣你就不會忘掉自增 i,這確實是一個很好的改進。現在回到原來的問題,我們目標是在數組的每個元素上運行 oodlify() 函數,并且將結果放到一個新的數組中。

對一個數組中每個元素都進行操作的這種模式也是非常普遍的。因此在 ES2015 中,引入了一種新的循環結構可以把計數器也簡化掉: for...of 循環。每一次返回數組的下一個元素給你,代碼如下:

let output = [];for (let item of input) { let newItem = oodlify(item); output.push(newItem);}

這樣就清晰很多了,注意這里計數器和比較都不用了,你甚至都不用把元素從數組里面取出來。for...of 幫我們做了里面的臟活累活。如果現在用 for...of 來代替所有的 for 循環,其實就可以很大程度上降低復雜性。但是,我們還可以做進一步的優化。

mapping

for...of 循環比 for 循環更清晰,但是依然需要一些配置性的代碼。如不得不初始化一個 output 數組并且每次循環都要調用 push() 函數。但有辦法可以讓代碼更加簡潔有力,我們先擴展一下問題。

如果有兩個數組需要調用 oodlify 函數會怎么樣?

const fellowship = [ 'frodo', 'sam', 'gandalf', 'aragorn', 'boromir', 'legolas', 'gimli',];const band = [ 'John', 'Paul', 'George', 'Ringo',];

很容易想到的方法是對每個數組都做循環:

let bandoodle = [];for (let item of band) { let newItem = oodlify(item); bandoodle.push(newItem);}let floodleship = [];for (let item of fellowship) { let newItem = oodlify(item); floodleship.push(newItem);}

這確實ok,有能正確執行的代碼,就比沒有好。但是重復的代碼太多了――不夠“DRY”。我們來重構它以降低重復性,創建一個函數:

function oodlifyArray(input) { let output = []; for (let item of input) {  let newItem = oodlify(item);  output.push(newItem); } return output;}let bandoodle = oodlifyArray(band);let floodleship = oodlifyArray(fellowship);

這看起來好多了,可是如果我們想使用另外一個函數該怎么辦?

function izzlify(s) { return s.replace(/[aeiou]+/g, 'izzle');}

上面的 oodlifyArray() 一點用都沒有了。但如果再創建一個 izzlifyArray() 函數的話,代碼又重復了。不管那么多,先寫出來看看什么效果:

function oodlifyArray(input) { let output = []; for (let item of input) {  let newItem = oodlify(item);  output.push(newItem); } return output;}function izzlifyArray(input) { let output = []; for (let item of input) {  let newItem = izzlify(item);  output.push(newItem); } return output;}

這兩個函數驚人的相似。那么是不是可以把它們抽象成一個通用的模式呢?我們想要的是:給定一個函數和一個數組,通過這個函數,把數組中的每一個元素做操作后放到新的數組中。我們把這個模式叫做 map 。一個數組的 map 函數如下:

function map(f, a) { let output = []; for (let item of a) {  output.push(f(item)); } return output;}

這里還是用了循環結構,如果想要完全擺脫循環的話,可以做一個遞歸的版本出來:

function map(f, a) { if (a.length === 0) { return []; } return [f(a[0])].concat(map(f, a.slice(1)));}

遞歸解決方法非常優雅,僅僅用了兩行代碼,幾乎沒有縮進。但是通常并不提倡于在這里使用遞歸,因為在較老的瀏覽器中的遞歸性能非常差。實際上,map 完全不需要你自己去手動實現(除非你自己想寫)。map 模式很常用,因此 JavaScript 提供了一個內置 map 方法。使用這個 map 方法,上面的代碼變成了這樣:

let bandoodle  = band.map(oodlify);let floodleship = fellowship.map(oodlify);let bandizzle  = band.map(izzlify);let fellowshizzle = fellowship.map(izzlify);

可以注意到,縮進消失,循環消失。當然循環可能轉移到了其他地方,但是我們已經不需要去關心它們了?,F在的代碼簡潔有力,完美。

為什么這個代碼這么簡單呢?這可能是個很傻的問題,不過也請思考一下。是因為短嗎?不是,簡潔并不代表不復雜。它的簡單是因為我們把問題分離了。有兩個處理字符串的函數: oodlify 和 izzlify,這些函數并不需要知道關于數組或者循環的任何事情。同時,有另外一個函數:map ,它來處理數組,它不需要知道數組中元素是什么類型的,甚至你想對數組做什么也不用關心。它只需要執行我們所傳遞的函數就可以了。把對數組的處理中和對字符串的處理分離開來,而不是把它們都混在一起。這就是為什么說上面的代碼很簡單。

reducing

現在,map 已經得心應手了,但是這并沒有覆蓋到每一種可能需要用到的循環。只有當你想創建一個和輸入數組同樣長度的數組時才有用。但是如果你想要向數組中增加幾個元素呢?或者想找一個列表中的最短字符串是哪個?其實有時我們對數組進行處理,最終只想得到一個值而已。

來看一個例子,現在一個數組里面存放了一堆超級英雄:

const heroes = [ {name: 'Hulk', strength: 90000}, {name: 'Spider-Man', strength: 25000}, {name: 'Hawk Eye', strength: 136}, {name: 'Thor', strength: 100000}, {name: 'Black Widow', strength: 136}, {name: 'Vision', strength: 5000}, {name: 'Scarlet Witch', strength: 60}, {name: 'Mystique', strength: 120}, {name: 'Namora', strength: 75000},];

現在想找最強壯的超級英雄。使用 for...of 循環,像這樣:

let strongest = {strength: 0};for (hero of heroes) { if (hero.strength > strongest.strength) {  strongest = hero; }}

雖然這個代碼可以正確運行,可是實在太爛了??催@個循環,每次都保存到目前為止最強的英雄。繼續提需求,接下來我們想要所有超級英雄的總強度:

let combinedStrength = 0;for (hero of heroes) { combinedStrength += hero.strength;}

在這兩個例子中,都在循環開始之前初始化了一個變量。然后在每一次的循環中,處理一個數組元素并且更新這個變量。為了使這種循環套路變得更加明顯一點,現在把數組中間的部分抽離到一個函數當中。并且重命名這些變量,以進一步突出相似性。

function greaterStrength(champion, contender) { return (contender.strength > champion.strength) ? contender : champion;}function addStrength(tally, hero) { return tally + hero.strength;}const initialStrongest = {strength: 0};let working = initialStrongest;for (hero of heroes) { working = greaterStrength(working, hero);}const strongest = working;const initialCombinedStrength = 0;working = initialCombinedStrength;for (hero of heroes) { working = addStrength(working, hero);}const combinedStrength = working;

用這種方式來寫,兩個循環變得非常相似了。它們兩個之間唯一的區別是調用的函數和初始值不同。兩個的功能都是對數組進行處理,最終得到一個值。所以,我們創建一個 reduce 函數來封裝這個模式。

function reduce(f, initialVal, a) { let working = initialVal; for (item of a) {  working = f(working, item); } return working;}

reduce 模式在 JavaScript 中也是很常用的,因此 JavaScript 為數組提供了內置的方法,不需要自己來寫。通過內置方法,代碼就變成了:

const strongestHero = heroes.reduce(greaterStrength, {strength: 0});const combinedStrength = heroes.reduce(addStrength, 0);

ok,如果足夠細心的話,你會注意到上面的代碼其實并沒有短很多。不過也確實比自己手寫的 reduce 代碼少寫了幾行。但是我們的目標并不是使代碼變短或者少寫,而是降低代碼復雜度?,F在的復雜度降低了嗎?我會說是的。把處理每個元素的代碼和處理循環代碼分離開來了,這樣代碼就不會互相糾纏在一起了,降低了復雜度。

reduce 方法乍一看可能覺得非?;A。我們舉的 reduce 大部分也比如做加法這樣的簡單例子。但是沒有人說 reduce 方法只能返回基本類型,它可以是一個 object 類型,甚至可以是另一個數組。當我第一次意識到這個問題的時候,自己也是豁然開朗。所以其實可以用 reduce 方法來實現 map 或者 filter,這個留給讀者自己做練習。

filtering

現在我們有了 map 處理數組中的每個元素,有了 reduce 可以處理數組最終得到一個值。但是如果想獲取數組中的某些元素該怎么辦?我們來進一步探索,現在增加一些屬性到上面的超級英雄數組中:

const heroes = [ {name: 'Hulk', strength: 90000, sex: 'm'}, {name: 'Spider-Man', strength: 25000, sex: 'm'}, {name: 'Hawk Eye', strength: 136, sex: 'm'}, {name: 'Thor', strength: 100000, sex: 'm'}, {name: 'Black Widow', strength: 136, sex: 'f'}, {name: 'Vision', strength: 5000, sex: 'm'}, {name: 'Scarlet Witch', strength: 60, sex: 'f'}, {name: 'Mystique', strength: 120, sex: 'f'}, {name: 'Namora', strength: 75000, sex: 'f'},];

ok,現在有兩個問題,我們想要:

找到所有的女性英雄;
找到所有能量值大于500的英雄。
使用普通的 for...of 循環,會得到如下代碼:

let femaleHeroes = [];for (let hero of heroes) { if (hero.sex === 'f') {  femaleHeroes.push(hero); }}let superhumans = [];for (let hero of heroes) { if (hero.strength >= 500) {  superhumans.push(hero); }}

邏輯嚴密,看起來還不錯?但是里面又出現了重復的情況。實際上,區別在于 if 的判斷語句,那么能不能把 if 語句重構到一個函數中呢?

function isFemaleHero(hero) { return (hero.sex === 'f');}function isSuperhuman(hero) { return (hero.strength >= 500);}let femaleHeroes = [];for (let hero of heroes) { if (isFemaleHero(hero)) {  femaleHeroes.push(hero); }}let superhumans = [];for (let hero of heroes) { if (isSuperhuman(hero)) {  superhumans.push(hero); }}

這種只返回 true 或者 false 的函數,我們一般把它稱作斷言(predicate)函數。這里用了斷言(predicate)函數來判斷是否需要保留當前的英雄。

上面代碼的寫法會看起來比較長,但是把斷言函數抽離出來,可以讓重復的循環代碼更加明顯?,F在把種循環抽離到一個函數當中。

function filter(predicate, arr) { let working = []; for (let item of arr) {  if (predicate(item)) {   working = working.concat(item);  } }}const femaleHeroes = filter(isFemaleHero, heroes);const superhumans = filter(isSuperhuman, heroes);

同 map 和 reduce 一樣,JavaScript 提供了一個內置數組方法,沒必要自己來實現(除非你自己想寫)。用內置數組方法,上面的代碼就變成了:

const femaleHeroes = heroes.filter(isFemaleHero);const superhumans = heroes.filter(isSuperhuman);

為什么這段代碼比 for...of 循環好呢?回想一下整個過程,我們要解決一個“找到滿足某一條件的所有英雄”。使用 filter 使得問題變得簡單化了。我們需要做的就是通過寫一個簡單函數來告訴 filter 哪一個數組元素要保留。不需要考慮數組是什么樣的,以及繁瑣的中間變量。取而代之的是一個簡單的斷言函數,僅此而已。

與其他的迭代函數相比,使用 filter 是一個四兩撥千斤的過程。我們不需要通讀循環代碼來理解到底要過濾什么,要過濾的東西就在傳遞給它的那個函數里面。

finding

filter 已經信手拈來了吧。這時如果只想找一個英雄該怎么辦?比如找 “Black Widow”。使用 filter 會這樣寫:

function isBlackWidow(hero) { return (hero.name === 'Black Widow');}const blackWidow = heroes.filter(isBlackWidow)[0];

這段代碼的問題是效率不夠高。filter 會檢查數組中的每一個元素,而我們知道這里面只有一個 “Black Widow”,當找到她的時候就可以停住,不用再看后面的元素了。那么,依舊利用斷言函數,我們寫一個 find 函數來返回第一次匹配上的元素。

function find(predicate, arr) { for (let item of arr) {  if (predicate(item)) {   return item;  } }}const blackWidow = find(isBlackWidow, heroes);

同樣地,JavaScript 已經提供了這樣的方法:

const blackWidow = heroes.find(isBlackWidow);

find 再次體現了四兩撥千斤的特點。通過 find 方法,把問題簡化為:你只要關注如何判斷你要找的東西就可以了,不必關心迭代到底怎么實現等細節問題。

總結

這些迭代函數的例子很好地詮釋“抽象”的作用和優雅?;叵胍幌挛覀兯v的內置方法,每個例子中我們都做了三件事:

消除了循環結構,使得代碼變的簡潔易讀;
通過適當的方法名稱來描述我們使用的模式,也就是:map,reduce,filter 和 find;
把問題從處理整個數組簡化到處理每個元素。
注意在每一種情況下,我們都用幾個純函數來分解問題和解決問題。真正令人興奮的是通過僅僅這么四種模式模式(當然還有其他的模式,也建議大家去學習一下),在 JS 代碼中你就可以消除幾乎所有的循環了。這是因為 JS 中幾乎每個循環都是用來處理數組,或者生成數組的。通過消除循環,降低了復雜性,也使得代碼的可維護性更強。

作者:James Sinclair 
編譯:胡子大哈

翻譯原文:http://huziketang.com/blog/posts/detail?postId=58ad37c3204d50674934c3ab 
英文原文:JAVASCRIPT WITHOUT LOOPS

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
成人亚洲激情网| 久久久久久久久久久成人| 精品露脸国产偷人在视频| 国产精品九九九| 中文字幕日韩在线观看| 日本sm极度另类视频| 亚洲综合色激情五月| 日韩在线观看高清| 日韩电影中文字幕av| 国模私拍一区二区三区| 一本色道久久88综合亚洲精品ⅰ| 欧美xxxx做受欧美.88| 久久香蕉国产线看观看网| 国产视频精品在线| 国产日韩av在线| 国产suv精品一区二区| 成人久久久久久久| 国产成人一区二区三区| 亚洲va欧美va在线观看| 久久亚洲精品成人| 国产成人精品久久二区二区| 欧美日韩在线视频一区二区| 8090成年在线看片午夜| 少妇高潮久久77777| 久久99亚洲热视| 国产91|九色| 午夜精品久久久久久久99热浪潮| 91亚洲精品一区| 久久精品男人天堂| 欧美另类极品videosbest最新版本| 日韩精品中文字| 亚洲**2019国产| 国产精品入口免费视| 精品丝袜一区二区三区| 欧美日韩在线免费| 亚洲精品视频中文字幕| 亚洲美女视频网站| 欧美与黑人午夜性猛交久久久| 亚洲在线免费观看| 69精品小视频| 精品国产福利在线| 中文字幕精品一区二区精品| 日韩一区av在线| 久久国产精品免费视频| 久热精品视频在线观看| 久久艳片www.17c.com| 久久视频中文字幕| 日韩毛片在线看| 欧美人与物videos| 国产精品日韩专区| 久久噜噜噜精品国产亚洲综合| 性色av一区二区三区| 亚洲午夜av久久乱码| 亚洲激情电影中文字幕| 日韩av手机在线看| 久久免费视频网站| 欧美性在线视频| 亚洲精品自拍偷拍| 亚洲性生活视频| 成人春色激情网| 亚洲午夜色婷婷在线| 欧美视频在线观看免费网址| 国产裸体写真av一区二区| 日韩精品www| 精品久久久91| 北条麻妃在线一区二区| 国产精彩精品视频| 欧美黑人巨大精品一区二区| 91在线网站视频| 日韩欧美中文字幕在线观看| 亚洲国产精品va在线观看黑人| 亚洲成人久久一区| 欧美午夜无遮挡| 欧美成人精品在线视频| 欧美专区在线播放| 亚洲综合精品伊人久久| 日本精品久久中文字幕佐佐木| 亚洲精品影视在线观看| 国产精品久久久久久婷婷天堂| 久久精品视频va| 青草热久免费精品视频| 亚洲欧洲在线播放| 粗暴蹂躏中文一区二区三区| 亚洲最大的网站| 欧美电影免费在线观看| 精品国产鲁一鲁一区二区张丽| 91国产中文字幕| 色多多国产成人永久免费网站| 久久久www成人免费精品| 久久精品99国产精品酒店日本| 亚洲片在线观看| 68精品国产免费久久久久久婷婷| 国产精品专区h在线观看| 国产精品美女无圣光视频| 九九热精品视频| 久久久久久免费精品| 亚洲精品福利在线| 一区三区二区视频| 热99在线视频| 国产第一区电影| 国语自产精品视频在线看抢先版图片| 欧美日韩一区二区在线| 国产一区二区三区在线观看视频| 国产一区二区久久精品| 成人美女免费网站视频| 一区二区欧美激情| 午夜精品美女自拍福到在线| 亚洲免费视频观看| 国产亚洲精品美女| 欧美精品18videos性欧| 欧美亚洲视频在线观看| 国产精品视频网址| 91在线直播亚洲| 永久免费毛片在线播放不卡| 亚洲欧洲午夜一线一品| 欧美又大粗又爽又黄大片视频| 91在线视频成人| 亚洲第一偷拍网| 亚洲精品免费一区二区三区| 欧美日韩亚洲国产一区| 久久综合久久八八| 成人情趣片在线观看免费| 欧美丰满片xxx777| 精品一区精品二区| 九九久久久久久久久激情| 少妇激情综合网| 欧美激情性做爰免费视频| 精品国产一区久久久| 久久久av亚洲男天堂| 亚洲第一福利视频| 亚洲免费视频网站| 国产精品久久999| 欧美午夜片在线免费观看| 欧美极品少妇xxxxⅹ裸体艺术| 欧美性猛交xxxx乱大交3| 日韩影视在线观看| 亚洲美女激情视频| 欧美激情在线狂野欧美精品| 国产成人自拍视频在线观看| 亚洲欧美国产精品va在线观看| 国产精品网站大全| 国产精品看片资源| 欧美激情第一页xxx| 91在线免费看网站| 国产精品ⅴa在线观看h| 亚洲精品美女在线观看播放| 91性高湖久久久久久久久_久久99| 久久在精品线影院精品国产| 欧美一级片在线播放| 欧美高清在线视频观看不卡| 日韩视频免费看| 国产成人欧美在线观看| 亚洲欧美综合精品久久成人| 在线观看免费高清视频97| 国产激情999| 欧美激情视频给我| 91精品国产99久久久久久| 久久久噜噜噜久噜久久| 欧美中文在线视频| 欧美在线性视频| 久久久久久久国产精品| 欧美一级在线播放| 91色视频在线观看| 最近2019中文字幕在线高清|