日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

如何在 JS 代码中消灭 for 循环

發布時間:2025/3/21 javascript 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何在 JS 代码中消灭 for 循环 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Edit: 在我入職上一家公司的第一天,看到代碼庫里面一堆的 for 循環,內心有些崩潰,于是做了一次技術分享,展示怎樣在代碼中避免 for 循環。這篇文章是那次分享的總結。至于為什么我提倡避免 for 循環,參考我寫的這篇文章。本文并不完美,其中遞歸的部分其實不應該在生產環境中用的。重點其實應該是怎樣用 reduce 和其它高階函數,至于這些高階函數底層用的是 while 循環還是 for 循環,都不重要,我們可以不在乎這些細節。在實際寫代碼的時候,只要被允許,我都會盡量使用 Ramda。如果對 Ramda 感興趣,可參考我的另一篇文章優雅代碼指北 -- 巧用 Ramda

一,用好 filter,map,和其它 ES6 新增的高階遍歷函數


問題一:?將數組中的 falsy 值去除

const?arrContainsEmptyVal?=?[3,?4,?5,?2,?3,?undefined,?null,?0,?""];

答案:

const?compact?=?arr?=>?arr.filter(Boolean);

?

問題二:?將數組中的 VIP 用戶余額加 10

const?users?=?[{?username:?"Kelly",?isVIP:?true,?balance:?20?},{?username:?"Tom",?isVIP:?false,?balance:?19?},{?username:?"Stephanie",?isVIP:?true,?balance:?30?} ];

答案:

users.map(user?=>?(user.isVIP???{?...user,?balance:?user.balance?+?10?}?:?user) );

補充:經網友提醒,這個答案存在淺拷貝的問題。操作引用型數據確實是一個麻煩的問題。下面提供兩個方案:

  • 用 Ramda:

    import?R?from?"ramda";const?add10IfVIP?=?R.ifElse(R.propEq("isVIP",?true),R.evolve({?balance:?R.add(10)?}),R.identity );const?updateUsers?=?R.map(add10IfVIP); updateUsers(users);
  • 用 Immer

    如果你習慣寫 mutable 的代碼,可以試下 Immer,用 mutable 的風格寫 immutable 的代碼。

    import?produce?from?"immer";const?updatedUsers?=?produce(users,?nextState?=>?{nextState.forEach(user?=>?{if?(user.isVIP)?{user.balance?+=?10;}}); });
  • ?

    問題三:?判斷字符串中是否含有元音字母

    const?randomStr?=?"hdjrwqpi";

    答案:

    const?isVowel?=?char?=>?["a",?"e",?"o",?"i",?"u"].includes(char); const?containsVowel?=?str?=>?[...str].some(isVowel);containsVowel(randomStr);

    ?

    問題四:?判斷用戶是否全部是成年人

    const?users?=?[{?name:?"Jim",?age:?23?},{?name:?"Lily",?age:?17?},{?name:?"Will",?age:?25?} ];

    答案:

    users.every(user?=>?user.age?>=?18);

    ?

    問題五:?找出上面用戶中的第一個未成年人

    答案:

    const?findTeen?=?users?=>?users.find(user?=>?user.age?<?18);findTeen(users);

    ?

    問題六:?將數組中重復項清除

    const?dupArr?=?[1,?2,?3,?3,?3,?3,?6,?7];

    答案:

    const?uniq?=?arr?=>?[...new?Set(arr)];uniq(dupArr);

    ?

    問題七:?生成由隨機整數組成的數組,數組長度和元素大小可自定義

    答案:

    const?genNumArr?=?(length,?limit)?=>Array.from({?length?},?_?=>?Math.floor(Math.random()?*?limit));genNumArr(10,?100);

    ?

    二,理解和熟練使用 reduce


    問題八:?不借助原生高階函數,定義 reduce

    答案:

    const?reduce?=?(f,?acc,?arr)?=>?{if?(arr.length?===?0)?return?acc;const?[head,?...tail]?=?arr;return?reduce(f,?f(head,?acc),?tail); };

    ?

    問題九:?將多層數組轉換成一層數組

    const?nestedArr?=?[1,?2,?[3,?4,?[5,?6]]];

    答案:

    const?flatten?=?arr?=>arr.reduce((flat,?next)?=>?flat.concat(Array.isArray(next)???flatten(next)?:?next),[]);

    ?

    問題十:?將下面數組轉成對象,key/value 對應里層數組的兩個值

    const?objLikeArr?=?[["name",?"Jim"],?["age",?18],?["single",?true]];

    答案:

    const?fromPairs?=?pairs?=>pairs.reduce((res,?pair)?=>?((res[pair[0]]?=?pair[1]),?res),?{});fromPairs(objLikeArr);

    ?

    問題十一:?取出對象中的深層屬性

    const?deepAttr?=?{?a:?{?b:?{?c:?15?}?}?};

    答案:

    const?pluckDeep?=?path?=>?obj?=>path.split(".").reduce((val,?attr)?=>?val[attr],?obj);pluckDeep("a.b.c")(deepAttr);

    ?

    問題十二:?將用戶中的男性和女性分別放到不同的數組里:

    const?users?=?[{?name:?"Adam",?age:?30,?sex:?"male"?},{?name:?"Helen",?age:?27,?sex:?"female"?},{?name:?"Amy",?age:?25,?sex:?"female"?},{?name:?"Anthony",?age:?23,?sex:?"male"?}, ];

    答案:

    const?partition?=?(arr,?isValid)?=>arr.reduce(([pass,?fail],?elem)?=>isValid(elem)???[[...pass,?elem],?fail]?:?[pass,?[...fail,?elem]],[[],?[]],);const?isMale?=?person?=>?person.sex?===?"male";const?[maleUser,?femaleUser]?=?partition(users,?isMale);

    ?

    問題十三:?reduce 的計算過程,在范疇論里面叫 catamorphism,即一種連接的變形。和它相反的變形叫 anamorphism。現在我們定義一個和 reduce 計算過程相反的函數 unfold(注:reduce 在 Haskell 里面叫 fold,對應 unfold)

    const?unfold?=?(f,?seed)?=>?{const?go?=?(f,?seed,?acc)?=>?{const?res?=?f(seed);return?res???go(f,?res[1],?acc.concat(res[0]))?:?acc;};return?go(f,?seed,?[]); };

    根據這個 unfold 函數,定義一個 Python 里面的 range 函數。

    答案:

    const?range?=?(min,?max,?step?=?1)?=>unfold(x?=>?x?<?max?&&?[x,?x?+?step],?min);

    ?

    三,用遞歸代替循環(可以break!)


    Edit: 雖然遞歸爆棧的問題可以用代碼解決,但遞歸確實性能趕不上循環。這部分內容純粹當做遞歸函數案例了。如何解決遞歸爆棧,可以參考我的另一篇文章不懂遞歸?讀完這篇保證你懂

    問題十四:?將兩個數組每個元素一一對應相加。注意,第二個數組比第一個多出兩個,不要把第二個數組遍歷完。

    const?num1?=?[3,?4,?5,?6,?7]; const?num2?=?[43,?23,?5,?67,?87,?3,?6];

    答案:

    const?zipWith?=?f?=>?xs?=>?ys?=>?{if?(xs.length?===?0?||?ys.length?===?0)?return?[];const?[xHead,?...xTail]?=?xs;const?[yHead,?...yTail]?=?ys;return?[f(xHead)(yHead),?...zipWith(f)(xTail)(yTail)]; };const?add?=?x?=>?y?=>?x?+?y;zipWith(add)(num1)(num2);

    ?

    問題十五:?將 Stark 家族成員提取出來。注意,目標數據在數組前面,使用 filter 方法遍歷整個數組是浪費。

    const?houses?=?["Eddard?Stark","Catelyn?Stark","Rickard?Stark","Brandon?Stark","Rob?Stark","Sansa?Stark","Arya?Stark","Bran?Stark","Rickon?Stark","Lyanna?Stark","Tywin?Lannister","Cersei?Lannister","Jaime?Lannister","Tyrion?Lannister","Joffrey?Baratheon" ];

    答案:

    const?takeWhile?=?f?=>?([head,?...tail])?=>f(head)???[head,?...takeWhile(f)(tail)]?:?[];const?isStark?=?name?=>?name.toLowerCase().includes("stark");takeWhile(isStark)(houses);

    ?

    問題十六:?找出數組中的奇數,然后取出前4個:

    const?numList?=?[1,?3,?11,?4,?2,?5,?6,?7];

    答案:

    const?takeFirst?=?(limit,?f,?arr)?=>?{if?(limit?===?0?||?arr.length?===?0)?return?[];const?[head,?...tail]?=?arr;return?f(head)??[head,?...takeFirst(limit?-?1,?f,?tail)]:?takeFirst(limit,?f,?tail); };const?isOdd?=?n?=>?n?%?2?===?1;takeFirst(4,?isOdd,?numList);

    ?

    四,使用高階函數遍歷數組時可能遇到的陷阱


    問題十七:?從長度為 100 萬的隨機整數組成的數組中取出偶數,再把所有數字乘以 3

    //?用我們剛剛定義的輔助函數來生成符合要求的數組 const?bigArr?=?genNumArr(1e6,?100);

    能運行的答案:

    const?isEven?=?num?=>?num?%?2?===?0; const?triple?=?num?=>?num?*?3;bigArr.filter(isEven).map(triple);

    注意,上面的解決方案將數組遍歷了兩次,無疑是浪費。如果寫 for 循環,只用遍歷一次:

    const?results?=?[]; for?(let?i?=?0;?i?<?bigArr.length;?i++)?{if?(isEven(bigArr[i]))?{results.push(triple(bigArr[i]));} }

    在我的電腦上測試,先 filter 再 map 的方法耗時 105.024 ms,而采用 for 循環的方法耗時僅 25.598 ms!那是否說明遇到此類情況必須用 for 循環解決呢? No!

    ?

    五,死磕到底,Transduce!

    我們先用 reduce 來定義 filter 和 map,至于為什么這樣做等下再解釋。

    const?filter?=?(f,?arr)?=>arr.reduce((acc,?val)?=>?(f(val)?&&?acc.push(val),?acc),?[]);const?map?=?(f,?arr)?=>?arr.reduce((acc,?val)?=>?(acc.push(f(val)),?acc),?[]);

    重新定義的 filter 和 map 有共有的邏輯。我們把這部分共有的邏輯叫做 reducer。有了共有的邏輯后,我們可以進一步地抽象,把 reducer 抽離出來,然后傳入 filter 和 map:

    const?filter?=?f?=>?reducer?=>?(acc,?value)?=>?{if?(f(value))?return?reducer(acc,?value);return?acc; };const?map?=?f?=>?reducer?=>?(acc,?value)?=>?reducer(acc,?f(value));

    現在 filter 和 map 的函數 signature 一樣,我們就可以進行函數組合(function composition)了。

    const?pushReducer?=?(acc,?value)?=>?(acc.push(value),?acc);bigNum.reduce(map(triple)(filter(isEven)(pushReducer)),?[]);

    但是這樣嵌套寫法易讀性太差,很容易出錯。我們可以寫一個工具函數來輔助函數組合:

    const?pipe?=?(...fns)?=>?(...args)?=>?fns.reduce((fx,?fy)?=>?fy(fx),?...args);

    然后我們就可以優雅地組合函數了:

    bigNum.reduce(pipe(filter(isEven),map(triple))(pushReducer),[] );

    經過測試(用?console.time()/console.timeEnd()),上面的寫法耗時 33.898 ms,僅比 for 循環慢 8 ms。為了代碼的易維護性和易讀性,這點性能上的微小犧牲,我認為是可以接受的。

    這種寫法叫 transduce。有很多工具庫提供了 transducer 函數。比如?transducers-js。除了用 transducer 來遍歷數組,還能用它來遍歷對象和其它數據集。功能相當強大。

    ?

    六,for 循環和 for … of 循環的區別


    for … of 循環是在 ES6 引入 Iterator 后,為了遍歷 Iterable 數據類型才產生的。EcmaScript 的 Iterable 數據類型有數組,字符串,Set 和 Map。for … of 循環屬于重型的操作(具體細節我也沒了解過),如果用 AirBNB 的 ESLint 規則,在代碼中使用 for … of 來遍歷數組是會被禁止的。

    那么,for … of 循環應該在哪些場景使用呢?目前我發現的合理使用場景是遍歷自定義的 Iterable。來看這個題目:

    問題十八:?將 Stark 家族成員名字遍歷,每次遍歷暫停一秒,然后將當前遍歷的名字打印來,遍歷完后回到第一個元素再重新開始,無限循環。

    const?starks?=?["Eddard?Stark","Catelyn?Stark","Rickard?Stark","Brandon?Stark","Rob?Stark","Sansa?Stark","Arya?Stark","Bran?Stark","Rickon?Stark","Lyanna?Stark" ];

    答案:

    function*?repeatedArr(arr)?{let?i?=?0;while?(true)?{yield?arr[i++?%?arr.length];} }const?infiniteNameList?=?repeatedArr(starks);const?wait?=?ms?=>new?Promise(resolve?=>?{setTimeout(()?=>?{resolve();},?ms);});(async?()?=>?{for?(const?name?of?infiniteNameList)?{await?wait(1000);console.log(name);} })();

    ?

    七,放棄倔強,實在需要用 for 循環了


    前面講到的問題基本覆蓋了大部分需要使用 for 循環的場景。那是否我們可以保證永遠不用 for 循環呢?其實不是。我講了這么多,其實是在鼓勵大家不要寫 for 循環,而不是不用 for 循環。我們常用的數組原型鏈上的 map,filter 等高階函數,底層其實是用 for 循環實現的。在需要寫一些底層代碼的時候,還是需要寫 for 循環的。來看這個例子:

    Number.prototype[Symbol.iterator]?=?function*()?{for?(let?i?=?0;?i?<=?this;?i++)?{yield?i;} };[...6];?//?[0,?1,?2,?3,?4,?5,?6]

    注意,這個例子只是為了好玩。生產環境中不要直接修改 JS 內置數據類型的原型鏈。原因是 V8 引擎有一個原型鏈快速推測機制,修改原型鏈會破壞這個機制,造成性能問題。

    總結

    以上是生活随笔為你收集整理的如何在 JS 代码中消灭 for 循环的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。