讀古今文學網 > 學習JavaScript數據結構與算法(第2版) > 2.7 JavaScript的數組方法參考 >

2.7 JavaScript的數組方法參考

在JavaScript裡,數組是可修改的對象,這意味著創建的每個數組都有一些可用的方法。數組很有趣,因為它們十分強大,並且相比其他語言中的數組,JavaScript中的數組有許多很好用的方法。這樣就不用再為它開發一些基本功能了,例如在數據結構的中間添加或刪除元素。

下面的表格中詳述了數組的一些核心方法,其中的一些我們已經學習過了。

方法名

描述

concat

連接2個或更多數組,並返回結果

every

對數組中的每一項運行給定函數,如果該函數對每一項都返回true,則返回true

filter

對數組中的每一項運行給定函數,返回該函數會返回true的項組成的數組

forEach

對數組中的每一項運行給定函數。這個方法沒有返回值

join

將所有的數組元素連接成一個字符串

indexOf

返回第一個與給定參數相等的數組元素的索引,沒有找到則返回

1

lastIndexOf

返回在數組中搜索到的與給定參數相等的元素的索引裡最大的值

map

對數組中的每一項運行給定函數,返回每次函數調用的結果組成的數組

reverse

顛倒數組中元素的順序,原先第一個元素現在變成最後一個,同樣原先的最後一個元素變成了現在的第一個

slice

傳入索引值,將數組裡對應索引範圍內的元素作為新數組返回

some

對數組中的每一項運行給定函數,如果任一項返回true,則返回true

sort

按照字母順序對數組排序,支持傳入指定排序方法的函數作為參數

toString

將數組作為字符串返回

valueOf

toString類似,將數組作為字符串返回

我們已經學過了pushpopshiftunshiftsplice方法。下面來看表格中提到的方法。在本書接下來的章節裡,編寫數據結構和算法時會大量用到這些方法。

2.7.1 數組合併

考慮如下場景:有多個數組,需要合併起來成為一個數組。我們可以迭代各個數組,然後把每個元素加入最終的數組。幸運的是,JavaScript已經給我們提供了解決方法,叫作concat方法:

var zero = 0;
var positiveNumbers = [1,2,3];
var negativeNumbers = [-3,-2,-1];
var numbers = negativeNumbers.concat(zero, positiveNumbers);

  

concat方法可以向一個數組傳遞數組、對像或是元素。數組會按照該方法傳入的參數順序連接指定數組。在這個例子裡,zero將被合併到nagativeNumbers中,然後positiveNumbers繼續被合併。最後輸出的結果是-3、-2、-1、0、1、2、3。

2.7.2 迭代器函數

有時我們需要迭代數組中的元素。前面我們已經學過,可以用循環語句來處理,例如for語句。

JavaScript內置了許多數組可用的迭代方法。對於本節的例子,我們需要數組和函數。假如有一個數組,它值是從1到15,如果數組裡的元素可以被2整除(偶數),函數就返回true,否則返回false

var isEven = function (x) {
  // 如果x是2的倍數,就返回true
  console.log(x);
  return (x % 2 == 0) ? true : false;
};
var numbers = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];

  

 return (x % 2 == 0) ? true : false也可以寫成return (x % 2== 0)

  1. every方法迭代

    我們要嘗試的第一個方法是everyevery方法會迭代數組中的每個元素,直到返回false

    numbers.every(isEven);
    
      

    在這個例子裡,數組numbers的第一個元素是1,它不是2的倍數(1是奇數), 因此isEven 函數返回false,然後every執行結束。

  2. some方法迭代

    下一步,我們來看some方法。它和every的行為類似,不過some方法會迭代數組的每個元素,直到函數返回true

    numbers.some(isEven);
    
      

    在我們的例子裡,numbers數組中第一個偶數是2(第二個元素)。第一個被迭代的元素是1,isEven會返回false。第二個被迭代的元素是2,isEven返回true——迭代結束。

  3. forEach方法迭代

    如果要迭代整個數組,可以用forEach方法。它和使用for循環的結果相同:

    numbers.forEach(function(x){
      console.log((x % 2 == 0));
    });
      
  4. 使用mapfilter方法

    JavaScript還有兩個會返回新數組的遍歷方法。第一個是map

    var myMap = numbers.map(isEven);
    
      

    數組myMap裡的值是:[false, true, false, true, false, true, false, true, false, true, false, true, false, true, false]。它保存了傳入map方法的isEven函數的運行結果。這樣就很容易知道一個元素是否是偶數。比如,myMap[0]false,因為1不是偶數;而myMap[1]true,因為2是偶數。

    還有一個filter方法。它返回的新數組由使函數返回true的元素組成:

    var evenNumbers = numbers.filter(isEven);
    
      

    在我們的例子裡,evenNumbers數組中的元素都是偶數:[2, 4, 6, 8, 10, 12, 14]

  5. 使用reduce方法

    最後是reduce方法。reduce方法接收一個函數作為參數,這個函數有四個參數:previousValuecurrentValueindexarray。這個函數會返回一個將被疊加到累加器的值,reduce方法停止執行後會返回這個累加器。如果要對一個數組中的所有元素求和,這就很有用,比如:

    numbers.reduce(function(previous, current, index){
      return previous + current;
    });
    
      

    輸出將會是120。

     JavaScript的Array類還有另外兩個重要方法:mapreduce。這兩個方法名是自解釋的,這意味著map方法會依照給定函數對值進行映射,而reduce方法會依照函數規約數組包含的值。這三個方法(mapfilterreduce)是我們要在第11章學習的JavaScript函數式編程的基礎。

2.7.3 ECMAScript 6和數組的新功能

第1章提到過,ECMAScript 6(ES6或ES2015)和ECMAScript 7(ES7或ES2016)規範給JavaScript語言帶來了新的功能。

下表列出了ES6和ES7新增的數組方法。

方法

描述

@@iterator

返回一個包含數組鍵值對的迭代器對象,可以通過同步調用得到數組元素的鍵值對

copyWithin

複製數組中一系列元素到同一數組指定的起始位置

entries

返回包含數組所有鍵值對的@@iterator

includes

如果數組中存在某個元素則返回true,否則返回false。ES7新增

find

根據回調函數給定的條件從數組中查找元素,如果找到則返回該元素

findIndex

根據回調函數給定的條件從數組中查找元素,如果找到則返回該元素在數組中的索引

fill

用靜態值填充數組

from

根據已有數組創建一個新數組

keys

返回包含數組所有索引的@@iterator

of

根據傳入的參數創建一個新數組

values

返回包含數組中所有值的@@iterator

除了這些新的方法,還有一種用for...of循環來迭代數組的新做法,以及可以從數組實例得到的迭代器對象。

在後面的主題中,我們會演示所有的新功能。

  1. 使用forEach和箭頭函數迭代

    箭頭函數可以簡化使用forEach迭代數組元素的做法。代碼例子如下:

    numbers.forEach(function (x) {
      console.log(x % 2 == 0);
    });
    
      

    這段代碼可以簡化如下:

    numbers.forEach(x => {
      console.log((x % 2 == 0));
    });
      
  2. 使用for...of循環迭代

    你已經學過用for循環和forEach方法迭代數組。ES6還引入了迭代數組值的for...of循環,來看看它的用法:

    for (let n of numbers) {
      console.log((n % 2 == 0) ? 'even' : 'odd');
    }
    
      

     可以訪問https://goo.gl/qHYAN1運行上面的示例。

  3. 使用ES6新的迭代器(@@iterator

    ES6還為Array類增加了一個@@iterator屬性,需要通過Symbol.iterator來訪問。代碼如下:

    let iterator = numbers[Symbol.iterator];
    console.log(iterator.next.value); // 1
    console.log(iterator.next.value); // 2
    console.log(iterator.next.value); // 3
    console.log(iterator.next.value); // 4
    console.log(iterator.next.value); // 5
    
      

    然後,不斷調用迭代器的next方法,就能依次得到數組中的值。numbers數組中有15個值,因此需要調用15次iterator.next.value

    數組中所有值都迭代完之後,iterator.next.value會返回undefined

    以上代碼的輸出和我們接下來要講的numbers.value是一樣的。

     訪問https://goo.gl/L81UQW查看和運行示例。

    • 數組的entrieskeysvalues方法

      ES6還增加了三種從數組中得到迭代器的方法。我們首先要學習的是entries方法。

      entries方法返回包含鍵值對的@@iterator,下面是使用這個方法的代碼示例:

      let aEntries = numbers.entries;   // 得到鍵值對的迭代器
      console.log(aEntries.next.value); // [0, 1] - 位置0的值為1
      console.log(aEntries.next.value); // [1, 2] - 位置1的值為2
      console.log(aEntries.next.value); // [2, 3] - 位置2的值為3
      
        

      numbers數組中都是數字,key是數組中的位置,value是保存在數組索引的值。

      使用集合、字典、散列表等數據結構時,能夠取出鍵值對是很有用的。這個功能會在本書後面的章節中大顯身手。

      keys方法返回包含數組索引的@@iterator,下面是使用這個方法的代碼示例:

      let aKeys = numbers.keys; // 得到數組索引的迭代器
      console.log(aKeys.next);  // {value: 0, done: false }
      console.log(aKeys.next);  // {value: 1, done: false }
      console.log(aKeys.next);  // {value: 2, done: false }
      
        

      keys方法會返回numbers數組的索引。一旦沒有可迭代的值,aKeys.next就會返回一個value屬性為undefineddone屬性為true的對象。如果done屬性的值為false,就意味著還有可迭代的值。

      values方法返回的@@iterator則包含數組的值。使用這個方法的代碼示例如下:

      let aValues = numbers.values;
      console.log(aValues.next); // {value: 1, done: false }
      console.log(aValues.next); // {value: 2, done: false }
      console.log(aValues.next); // {value: 3, done: false }
      
        

       記住,當前的瀏覽器還沒有完全支持ES6所有的新功能,因此,測試這些代碼最好的辦法是使用Babel。訪問https://goo.gl/eojEGk查看和運行示例。

  4. 使用from方法

    Array.from方法根據已有的數組創建一個新數組。比如,要複製numbers數組,可以這樣做:

    et numbers2 = Array.from(numbers);
    
      

    還可以傳入一個用來過濾值的函數,例子如下:

    let evens = Array.from(numbers, x => (x % 2 == 0));
    
      

    上面的代碼會創建一個evens數組,其中只包含numbers數組中的偶數。

     訪問https://goo.gl/n4rOY4查看和運行示例。

  5. 使用Array.of方法

    Array.of方法根據傳入的參數創建一個新數組。以下面的代碼為例:

    let numbers3 = Array.of(1);
    let numbers4 = Array.of(1, 2, 3, 4, 5, 6);
    
      

    它和下面這段代碼的效果一樣:

    let numbers3 = [1];
    let numbers4 = [1, 2, 3, 4, 5, 6];
    
      

    我們也可以用這個方法複製已有的數組,比如:

    let numbersCopy = Array.of(...numbers4);
    
      

    上面的代碼和Array.from(numbers4)的效果是一樣的,區別只是用到了第1章講過的展開操作符。展開操作符(...)會把numbers4數組裡的值都展開成參數。

     訪問https://goo.gl/FoJYNf查看和運行示例。

  6. 使用fill方法

    fill方法用靜態值填充數組。以下面的代碼為例:

    let numbersCopy = Array.of(1, 2, 3, 4, 5, 6);
    
      

    numbersCopy數組的length6,也就是有6個位置。再看下面的代碼:

    numbersCopy.fill(0);
    
      

    numbersCopy數組所有位置的值都會變成0[0, 0, 0, 0, 0, 0])。

    我們還可以指定開始填充的索引,如下:

    numbersCopy.fill(2, 1);
    
      

    上面的例子裡,數組中從1開始的所有位置,值都是2[0, 2, 2, 2, 2, 2])。

    同樣,也可以指定結束填充的索引:

    numbersCopy.fill(1, 3, 5);
    
      

    上面的例子裡,我們會把1填充到數組索引35的位置(不包括5),得到的數組為[0, 2, 2, 1, 1, 2]

    創建數組並初始化值的時候,fill方法非常好用,就像下面這樣:

    let ones = Array(6).fill(1);
    
      

    上面的代碼創建了一個長度為6,所有的值都是1的數組([1, 1, 1, 1, 1, 1])。

     訪問https://goo.gl/sqiHSK查看和運行示例。

  7. 使用copyWithin方法

    copyWithin方法複製數組中的一系列元素到同一數組指定的起始位置。看看下面這個例子:

    let copyArray = [1, 2, 3, 4, 5, 6];
    
      

    假如我們想把456三個值複製到數組前三個位置,得到[4, 5, 6, 4, 5, 6]這個數組。可以用下面的代碼達到目的:

    copyArray.copyWithin(0, 3);
    
      

    假如我們想把45兩個值(位置3和4)複製到位置1和2,可以這樣做:

    copyArray = [1, 2, 3, 4, 5, 6];
    copyArray.copyWithin(1, 3, 5);
    
      

    這種情況下,會把從位置3開始到位置5結束(不包括5)的元素複製到位置1,結果是得到數組[1, 4, 5, 4, 5, 6]

     訪問https://goo.gl/hZhBE1查看和運行示例。

2.7.4 排序元素

通過本書,我們能學到如何編寫最常用的搜索和排序算法。其實,JavaScript裡也提供了一個排序方法和一組搜索方法。讓我們來看看。

首先,我們想反序輸出數組numbers(它本來的排序是1, 2, 3, 4,…15)。要實現這樣的功能,可以用reverse方法,然後數組內元素就會反序。

numbers.reverse;

  

現在,輸出numbers的話就會看到[15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]。然後,我們用sort方法:

numbers.sort;

  

然而,如果輸出數組,結果會是[1, 10, 11, 12, 13, 14, 15, 2, 3, 4, 5, 6, 7, 8, 9]。看起來不大對,是吧?這是因為sort方法在對數組做排序時,把元素默認成字符串進行相互比較。

我們可以傳入自己寫的比較函數。因為數組裡都是數字,所以可以這樣寫:

numbers.sort(function(a, b){
  return a-b;
});

  

這段代碼,在b大於a時,會返回負數,反之則返回正數。如果相等的話,就會返回0。也就是說返回的是負數,就說明ab小,這樣sort就根據返回值的情況給數組做排序。

之前的代碼也可以被表示成這樣,會更清晰一些:

function compare(a, b) {
  if (a < b) {
    return -1;
  }
  if (a > b) {
    return 1;
  }
  // a必須等於b
  return 0;
}

numbers.sort(compare);

 

這是因為JavaScript的sort方法接受compareFunction作為參數,然後sort會用它排序數組。在例子裡,我們聲明了一個用來比較數組元素的函數,使數組按升序排序。

  1. 自定義排序

    我們可以對任何對像類型的數組排序,也可以創建compareFunction來比較元素。例如,對像Person有名字和年齡屬性,我們希望根據年齡排序,就可以這麼寫:

    var friends = [
      {name: 'John', age: 30},
      {name: 'Ana', age: 20},
      {name: 'Chris', age: 25}
    ];
     
    function comparePerson(a, b){
      if (a.age < b.age){
        return -1
      }
      if (a.age > b.age){
        return 1
      }
      return 0;
    }
     
    console.log(friends.sort(comparePerson));
    
      

    在這個例子裡,最後會輸出Ana(20), Chris(25), John(30)

  2. 字符串排序

    假如有這樣一個數組:

    var names =['Ana', 'ana', 'john', 'John'];
    console.log(names.sort);
    
      

    你猜會輸出什麼?答案是這樣的:

    ["Ana", "John", "ana", "john"]
    
      

    既然a在字母表裡排第一位,為何ana卻排在了John之後呢?這是因為JavaScript在做字符比較的時候,是根據字符對應的ASCII值來比較的。例如,A、J、a、j對應的ASCII值分別是65、75、97、106。

    雖然在字母表裡a是最靠前的,但J的ASCII值比a的小,所以排在a前面。

     想瞭解更多關於ASCII表的信息,請訪問http://www.asciitable.com/。

    現在,如果給sort傳入一個忽略大小寫的比較函數,將會輸出["Ana", "ana", "John", "john"]

    names.sort(function(a, b){
      if (a.toLowerCase < b.toLowerCase){
        return -1
      }
      if (a.toLowerCase > b.toLowerCase){
        return 1
      }
      return 0;
    });
    
      

    假如對帶有重音符號的字符做排序的話,我們可以用localCompare來實現:

    var names2 = ['Maeve', 'Maeve'];
    console.log(names2.sort(function(a, b){
        return a.localCompare(b);
    }));
    
      

    最後輸出的結果將是["Maeve", "Maeve"]

2.7.5 搜索

搜索有兩個方法:indexOf方法返回與參數匹配的第一個元素的索引,lastIndexOf返回與參數匹配的最後一個元素的索引。我們來看看之前用過的numbers數組:

console.log(numbers.indexOf(10));
console.log(numbers.indexOf(100));

  

在這個示例中,第一行的輸出是9,第二行的輸出是-1(因為100不在數組裡)。

下面的代碼會返回同樣的結果:

numbers.push(10);
console.log(numbers.lastIndexOf(10));
console.log(numbers.lastIndexOf(100));

  

我們往數組裡加入了一個新的元素10,因此第二行會輸出15(數組中的元素是1到15,還有10),第三行會輸出-1(因為100不在數組裡)。

  1. ECMAScript 6——findfindIndex方法

    看看下面這個例子:

    let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
    function multipleOf13(element, index, array) {
      return (element % 13 == 0) ? true : false;
    }
    console.log(numbers.find(multipleOf13));
    console.log(numbers.findIndex(multipleOf13));
    
      

    findfindIndex方法接收一個回調函數,搜索一個滿足回調函數條件的值。上面的例子裡,我們要從數組裡找一個13的倍數。

    findfindIndex的不同之處在於,find方法返回第一個滿足條件的值,findIndex方法則返回這個值在數組裡的索引。如果沒有滿足條件的值,find會返回undefined,而findIndex返回-1

     訪問https://goo.gl/2vAaCh查看和運行示例。

  2. ECMAScript 7——使用includes方法

    如果數組裡存在某個元素,includes方法會返回true,否則返回false。使用includes方法的例子如下:

    console.log(numbers.includes(15));
    console.log(numbers.includes(20));
    
      

    例子裡的includes(15)返回trueincludes(20)返回false,因為numbers數組裡沒有20

    如果給includes方法傳入一個起始索引,搜索會從索引指定的位置開始:

    let numbers2 = [7, 6, 5, 4, 3, 2, 1];
    console.log(numbers2.includes(4, 5));
    
      

    上面的例子輸出為false,因為數組索引5之後的元素不包含4

     訪問https://goo.gl/tTY9bc查看和運行示例。

2.7.6 輸出數組為字符串

現在,我們學習最後兩個方法:toStringjoin

如果想把數組裡所有元素輸出為一個字符串,可以用toString方法:

console.log(numbers.toString);

  

12345678910111213141510這些值都會在控制台中輸出。

如果想用一個不同的分隔符(比如-)把元素隔開,可以用join方法:

var numbersString = numbers.join('-');
console.log(numbersString);

  

這將輸出:

1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-10

  

如果要把數組內容發送到服務器,或進行編碼(知道了分隔符,解碼也很容易),這會很有用。

有一些很棒的資源可以幫助你更深入地瞭解數組及其方法。

  • 第一個是w3schools的數組頁面:http://www.w3schools.com/js/js_arrays.asp。

  • 第二個是w3schools的數組方法頁面:http://www.w3schools.com/js/js_array_methods.asp。

  • Mozilla的數組及其方法的頁面也非常棒,還有不錯的例子:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array(http://goo.gl/vu1diT)。

  • 在JavaScript項目中使用數組時,也有一些很棒的類庫。

    Underscore:http://underscorejs.org/

    Lo-Dash:http://lodash.com/-