從數組的遍歷方式談談代碼的語義性

一、前言

我之前的一篇文章介紹了幾種遍歷數組的方式,當然遠不止這幾種。不過今天想談的是別的東西,數組遍歷的具體方式和用法就不再贅述了。今天,我想以數組遍歷方式爲切入點,談一下代碼的語義性。當然,這只是個人理解。

首先,代碼語義性是什麼?從我的角度來說,語義性就是代碼本身呈現的、和具體功能無關的、表達代碼作用的程度,或者說是代碼直觀的程度。

別人看一段代碼,不看代碼具體的執行邏輯和過程,能理解你這段代碼的意圖越多,你的代碼語義性就越強。很多代碼規範,有一個作用就是增強代碼的語義性。比如類名命名規範、變量命名規範、函數命名規範、模塊命名規範等等。如果嚴格遵守這些規範,那麼別人看到你的代碼,看屬性名或者方法名,就知道這段邏輯的作用是什麼。比如,addCount這個方法名,表示這是一個增加數量的方法,count,表示統計一個東西的數量。

再比如h5新增的一些元素,section、article,header、footer等,一眼看去,就知道這些元素的作用,這就是代碼的語義性的一個體現。

js原生提供了很多遍歷的方法,在我看來,雖然其實用一種方法就能滿足所有的需求,但是應該根據不同的使用需求,選擇對應的方法。除了節約性能,更重要的是加強代碼的語義性和可讀性。

二、示例

以下簡單介紹一下數組遍歷方式以及它們使用場景的理解

1.原生的for循環和for in循環

const list = [1, 2, 3];
for (let i = 0; i < list.length; i++) {
  const item = list[i];
  //... do something
}

for循環,性能最強,沒有額外的操作,可隨時終止,不過寫起來最繁瑣,可讀性稍微差點。所以我覺得是所有遍歷方式中用來兜底的,現在用的比較少。另一個優點是可以continue或break,不過因爲有了後面出的for of,現在,按照我的理解,只有在同時需要用到item和index,並且還需要根據條件continue或者break的場景裏才適合用這個了。它另外一個優勢——性能——已經因爲越來越強大的js引擎導致被人們越來越少提及。

for in循環一般用來遍歷對象的鍵值對,數組的鍵就是下標,性能for循環差很多,因爲數組內部優化了迭代方式,for in是如果對象的鍵值對來遍歷的。缺點一樣,繁瑣,可讀性較差。這裏提一句,因爲es6新增的Object.values()、Object.keys()、Object.entries()這三個方法可以把對象轉換成對應的數組,所以for in出現的頻率也越來越低了。

2.for of循環

const list = [1, 2, 3];
for (const item of list) {
  //do something
  if (item >= 2) {
    break;
  }
}

es6新增的方法,因爲es6提出了iterator的概念,所以for of應運而生。它基本上就是es6版本的for循環,可以continue和break,有個小小的缺點是沒有內置的index,與數組內置的方法相比,寫起來還是稍微有一點繁瑣。如果你需要按需求終止或跳過循環,且不需要index,那麼for of循環是最合適的。

3.forEach

從這裏開始,介紹的方法都是Array.prototype上內置的,即每個數組實例都能調用的方法。他們都遵循了函數式編程的理念,把迭代的函數作爲參數傳遞進去,並且爲迭代函數提供了迭代項和index,且都不會改變原數組(這不意味着在迭代過程中修改成員也不會改變原數組)。

const list = [1, 2, 3];
list.forEach(item => {
  //do something
});

forEach的一個特點是沒有返回值,我個人理解爲純操作的一個函數,需要對數組每一項進行操作,且不需要返回值時,可以使用forEach方法。

4.map

const list = [1, 2, 3];
const newList = list.map((item, index) => item * index);
//newList=[0,2,6]

map方法有返回值,返回值是一個新的數組,新數組的每一項成員,都是對應的原數組每一項執行完迭代函數之後的返回值。換句話來說,map這裏是映射的意思,一個數組的成員按照相同的規則映射爲一個新的數組。所以,如果你需要在不改變原數組時得到一個原有數組的映射,可以使用map方法。

5.filter

const list = [1, 2, 3];
const newList = list.filter((item, index) => item * index >= 4);
//newList=[3]

filter方法有返回值,返回值是一個新的數組。原數組的每一項成員,執行迭代函數的返回值如果爲真值,則會將這個成員放進新數組,如果返回值爲假值,則不會放入新數組(假值就是和false==判斷爲true的值,是false、null、undefined、0,'',NaN,真值是除假值外的其他量)。這裏filter是過濾的意思,一個數組篩選出所有符合條件的成員。所以,如果你需要在不改變原數組時得到一個原數組的部分成員,那麼可以使用filter方法。

6.reduce和reduceRight

const list = [
  {
    id: 1,
    label: "萬里歸來顏愈少"
  },
  {
    id: 2,
    label: "笑時猶帶嶺梅香"
  },
  {
    id: 3,
    label: "試問嶺南應不好"
  },
  {
    id: 4,
    label: "此心安處是吾鄉"
  }
];
const res = list.reduce((res, item) => {
  res[item.id] = item;
  return res;
}, {});
//res={
//    '1': { id: 1, label: '萬里歸來顏愈少' },
//    '2': { id: 2, label: '笑時猶帶嶺梅香' },
//    '3': { id: 3, label: '試問嶺南應不好' },
//    '4': { id: 4, label: '此心安處是吾鄉' }
//    }

reduce方法有返回值,返回值的結果,我個人理解是原數組每一項成員對一個原始值進行操作。所有成員都執行一次迭代函數之後,原始值就變成了最終的結果。(如果每一輪迭代不會影響初始值,那麼爲什麼不用forEach呢)。具體場景呢,我曾經得到過一個數組,數組的每個成員有個id,爲了方便的根據id獲取某個成員,我用了reduce方法建立了id和數組成員的映射關係。

reduceRight和reduce的使用方式完全一樣,區別是reduceRight是從數組的最後一項成員開始執行迭代函數。

7.find和findIndex

const list = [
  {
    id: 1,
    label: "萬里歸來顏愈少"
  },
  {
    id: 2,
    label: "笑時猶帶嶺梅香"
  },
  {
    id: 3,
    label: "試問嶺南應不好"
  },
  {
    id: 4,
    label: "此心安處是吾鄉"
  }
];
const item = list.find(item => item.id === 2);
const index = list.findIndex(item => item.id === 2);
// item={id: 2,label: "笑時猶帶嶺梅香"}
// index=1

find方法有返回值,返回值的結果是第一個執行迭代函數的返回值爲真值的那一項成員。如果數組每一項成員執行完迭代函數的返回值都是假值,那麼find方法會返回undefined。所以,如果我們要找到原數組中第一個符合條件的成員,可以使用find方法。

findIndex方法和find方法的原理一樣,區別是findIndex方法返回的是第一個符合條件成員的index。如果沒有找到符合條件的成員,那麼findIndex的返回值是-1。

find和findIndex的一個優點是,確定返回的結果時就會立刻終止循環,所以不需要擔心會執行進行額外的循環

8.some和every

const list = [1, 2, 3];
const res = list.some(item => item >= 2);
// res = true

some方法有返回值,如果原數組的每一項成員執行迭代函數的結果,有一項爲真值,那麼some方法會返回true。反之,如果每一項成員執行迭代函數的返回值都是false,那麼some方法的返回值就是false。你可以把每一項成員執行迭代函數的返回值理解成或的關係。

const list = [1, 2, 3];
const res = list.every(item => item >= 2);
// res = false;

every方法有返回值,如果原數組的每一項成員執行迭代函數的結果,有一項爲假值,那麼every方法會返回false。反之,如果每一項成員執行迭代函數的返回值都是true,那麼every方法的返回值就是true。你可以把每一項成員執行迭代函數的返回值理解成且的關係。

some和every方法的一個優點是,只要確定了結果,就會立即返回結果,不需要擔心會執行進行額外的循環。

every和some都適合用於對數組成員進行一個條件判斷的場景。

三、結語

其實,數組遍歷,用一個方法就能實現所有的需求,只是有些需要額外的變量,有些需要額外執行幾次迭代而已。但是,除了這些性能上或者說代碼簡潔方面的考慮,更重要的,影響我們選擇用什麼方法的因素,我覺得是代碼是否直觀的問題,或者說是語義性。

實現功能的下一個階段是更好的實現功能,不管是從代碼角度還是從實現角度,做到更好。

開發能力,見於大,也見於小。實現很難的需求,很重要,組織好代碼,也很重要。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章