前端如何優雅處理類陣列物件?

pingan8787發表於2020-09-21

一、背景介紹

Leo 部門最近來了位前端實習生 Robin,作為師傅,Leo 認真的為 Robin 介紹了公司業務、部門工作等情況,還有前端的新人學習地圖。

接下來 Robin 開始一週愉快的學習啦~

一週後,Leo 為 Robin 同學佈置了學習作業,開發一個【人員搜尋選擇】的頁面,效果大致如下:

Robin 看完這個效果圖後,一臉得意的樣子,這確實不難呀~

過幾天后,Robin 帶著自己寫的程式碼,給 Leo 展示了她的程式碼,並疑惑的問到:

她將這個“陣列”輸出到控制檯:

Leo 看了看程式碼:

getUserList(){
   const memberList = $('#MemberList li');
   memberList.map(item => { console.log(item) });
   console.log(memberList);
}

Leo 又問到:

Robin 一臉疑惑,然後 Leo 再原來程式碼上,加了個 Array.from 方法如下:

getUserList(){
    const memberList = Array.from($('#MemberList li'));
    memberList.map(item => {
        console.log(item)
    })
    console.log(memberList)
}

然後重新執行程式碼,輸出下面結果:

Leo 輸出的結果,跟 Robin 說到:

Robin 滿臉期待望著師傅,對類陣列物件更加充滿期待。

二、類陣列物件介紹

2.1 概念介紹

所謂 類陣列物件,即格式與陣列結構類似,擁有 length 屬性,可以通過索引來訪問或設定裡面的元素,但是不能使用陣列的方法,就可以歸類為類陣列物件

舉個例子?:

const arrLike = {
  0: 'name',
  1: 'age',
  2: 'job',
  length: 3
}

2.2 常見類陣列物件

  • arguments 物件;
function f() {
  return arguments;
}
f(1,2,3)

// Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
  • NodeList(比如 document.getElementsByClassName('a') 得到的結果;
document.getElementsByTagName('img')
// HTMLCollection(3) [img, img, img]
  • typedArray(比如 Int32Array);

typedArray 即 型別化陣列物件 是一種類似陣列的物件,它提供了一種用於訪問原始二進位制資料的機制。JavaScript引擎會做一些內部優化,以便對陣列的操作可以很快。然而,隨著Web應用程式變得越來越強大,尤其一些新增加的功能例如:音訊視訊編輯,訪問WebSockets的原始資料等,很明顯有些時候如果使用JavaScript程式碼可以快速方便地通過型別化陣列來操作原始的二進位制資料將會非常有幫助。 —— 《MDN 型別化陣列》

const typedArray = new Uint8Array([1, 2, 3, 4])
// Uint8Array(4) [1, 2, 3, 4]

另外使用 jQuery 獲取元素,會被 jQuery 做特殊處理成為 init 型別:

$('img')
// init(3) [img, img, img, prevObject: init(1), context: document, selector: "img"]

當然還有一些不常見的類陣列物件,比如“Storage API 返回的結果”,這裡就不一一列出。

三、類陣列物件屬性

下面通過 Robin 程式碼作為示例,介紹類陣列物件的屬性:

const memberList = $('#MemberList li');

3.1 讀寫

// 讀取
memberList[0];
// Node: <li>...</li>

// 寫入
memberList[0] = document.createElement("div")
memberList[0];
//  Node: <div>...</div>

3.2 長度

memberList.length; 
// 10

3.3 遍歷

for (let i = 0;i < memberList.length; i++){
    console.log(memberList[i]);
}

/*
    Node: <li>...</li>
    Node: <li>...</li>
  ... 共10個,省略其他
*/

memberList.map(item => console.log(item));

/*
    0
  ... 共10個,省略其他
*/

但如果是 HTMLCollection 就不能使用 map 咯:

const img = document.getElementsByTagName("img");
img.map(item => console.log(item));

// Uncaught TypeError: img.map is not a function

四、類陣列物件處理

Leo 看了看 Robin 處理這個列表的程式碼:

getUserList(){
    const memberList = $('#MemberList li');
    const result = {
        text: [],
        dom : [],
    };
    memberList.map(item => {
        item = memberList[item]
        // 判斷當前節點是否有 checked 類名
    })
    console.log(result)
    this.showToast(`選中成員:${result.text}`);
}

很明顯,Robin 並沒有對 jQuery 獲取到的 memberList 做處理,直接使用,通過索引來獲取對應值。
Leo 繼續和 Robin 介紹到:

4.1 Array.from

使用 Array.from 來將類陣列物件轉為陣列物件,操作起來非常簡單:

getUserList(){
    const memberList = Array.from($('#MemberList li'));
    // 省略其他程式碼
}

語法如下:

Array.from(arrayLike[, mapFn[, thisArg]])

引數

  1. arrayLike 想要轉換成陣列的偽陣列物件或可迭代物件。
  2. mapFn 可選如果指定了該引數,新陣列中的每個元素會執行該回撥函式。
  3. thisArg 可選可選引數,執行回撥函式 mapFnthis 物件。

返回值
一個新的陣列例項。

更多 Array.from 介紹可以檢視文件。

4.2 Array.prototype.slice.call()

slice() 方法返回一個新的陣列物件,這一物件是一個由 beginend 決定的原陣列的淺拷貝(包括 begin,不包括end)。原始陣列不會被改變

實現程式碼:

getUserList(){
    const memberList = Array.prototype.slice.call($('#MemberList li'));
    // 省略其他程式碼
}

更多 Array.prototype.slice 介紹可以檢視文件。

4.3 ES6展開運算子

展開語法(Spread syntax), 可以在函式呼叫/陣列構造時, 將陣列表示式或者string在語法層面展開;還可以在構造字面量物件時, 將物件表示式按key-value的方式展開。

實現程式碼:

getUserList(){
    const memberList = [...document.getElementsByTagName("li")];
    // 省略其他程式碼
}

更多 ES6展開運算子 介紹可以檢視文件。

4.4 利用concat+apply

getUserList(){
    const memberList = Array.prototype.concat.apply([], $('#MemberList li'));
    // 省略其他程式碼
}

五、案例小結

Leo 介紹完這些知識後,Robin 又優化了下自己的程式碼,涉及到類陣列物件操作的核心 js 程式碼如下:

class SelectMember {
    constructor(){
        this.MockUsers = window.MockUsers;
        this.init();
    }
    init(){
        this.initMemberList('#MemberList', this.MockUsers);
        this.initBindEvent();
    }
      // ... 省略部分程式碼,保留核心程式碼
    submitSelect(){
        const memberList = Array.from($('#MemberList li'));
        const result = {
            text: [],
            dom : [],
        };
        memberList.map(item => {
            const hasClass = $(item).children('.round-checkbox').children('span').hasClass(this.selectClassName);
            if(hasClass){
                result.text.push($(item).children('.user-data').children('h4').text());
                result.dom.push(item);
            }
        })
        this.showToast(`選中成員:${result.text}`);
    }
}

let newMember = new SelectMember();

很明顯,使用正確方式來處理類陣列物件,不僅能使我們程式碼更加少,減少轉換處理,還能提高程式碼質量。

整個專案的完整程式碼,可以在我的 github 檢視

https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Demo/10.Learn-Array-Liked-Objects/index.html

六、總結

本文我們通過一個實際場景,詳細介紹了類陣列物件在實際開發中的使用,對於常見的類陣列物件,我們還介紹了處理方式,能很大程度減少我們處理類陣列物件的操作,將類陣列統一轉成陣列,更加方便對資料的操作。
希望看完本文的你,以後再遇到類陣列物件,不會再一臉懵逼咯~~~

相關文章