建立包含N個空物件的陣列

weixin_33935777發表於2019-01-21

在給物件設定屬性時, 如果物件不存在很容易報錯.

有些場景, 在對物件陣列處理時, 設定物件屬性前判斷物件是否存在. 與其這樣, 還不如直接初始化為空物件陣列.

本文主要涉及到一些容易忽略的知識點:

  1. Array.prototype.fill() 的使用.
  2. 簡單型別和複雜型別賦值/複製、傳參的區別.
  3. 空單元陣列的弊端.
  4. 箭頭函式中的 returnthis.
  5. Function.prototype.apply() 的非常規使用.

9個考生就來了6個


考試時, 每個考生都有自己位置. 考生對照著可以很容易在考場裡找到自己的座位.
秉著公平、公正、公開的原則, 考生被稀疏地散佈在考場的各個角落.
複製程式碼

假設考場 3 ✖️3 排列, 考生的資訊:

[{"row":1,"col":1,"name":"Ada"},
 {"row":3,"col":3,"name":"Aaron"},
 {"row":1,"col":2,"name":"Aditi"},
 {"row":3,"col":2,"name":"Aditi"},
 {"row":1,"col":3,"name":"Aditi"},
 {"row":3,"col":1,"name":"Abbott"}]
複製程式碼

將考場位置做成一個表格, 對考生位置按排統計, 來標註考生出勤情況.

[{"row":1,"col_1":"Ada","col_2":"Aditi","col_3":"Aditi"},
 {},
 {"row":3,"col_3":"Aaron","col_2":"Aditi","col_1":"Abbott"}]
複製程式碼

(為嘛沒有第二排? 自知考不過, 缺考了唄?) 開發中, 對原始資料進行處理是一件很平常的事. so, 這個資料的處理應該很簡單...吧?

Array(3).fill({}) 試一波


如何初始化空物件陣列?

原始資料是以學生個體的資訊儲存展示的, 現在則按排為單位對資料進行處理. 理所當然的會想到先初始化三個空物件陣列.

let studentRow = Array(3).fill({})
// > studentRow
// [ {}, {}, {} ]
複製程式碼

動作很快姿勢很帥. 不過, 這樣真的可以麼? 長得倒是像那麼一回事, 可實際上完全行不通. Array.prototype.fill() 的用法是, 指定某個值來填充陣列.

也就是說, {} 在 studentRow 裡複製了三次. 如果是簡單型別值倒也罷了, 但是換做複雜型別值, 修改每一個 {} , 都會影響其它的 {}. 因為它們都是對同一個物件的引用.

let studentRow = Array(3).fill({});
studentRow[0].name = 'tony';
// > studentRow
// [ { name: 'tony' },
//   { name: 'tony' },
//   { name: 'tony' } ]

// 等同於
let obj = {};
let studentRow = Array(3).fill(obj);
// > studentRow
// {obj, obj, obj}
studentRow[0].name = 'tony';
// > studentRow
// [ { name: 'tony' },
//   { name: 'tony' },
//   { name: 'tony' } ]
複製程式碼

知識點:

  • 將一個值賦予變數時, 解析器必須確定這個值是基本型別值還是複雜型別值.
  • 當是複雜型別值時, 變數裡儲存的是該複雜型別值在堆中的一個指標. 複製的是變數的指標, 操作的卻是實際的物件.

Array(3)map(() => {}) 結合有問題


Array(3).fill({}) 行不通. 那麼, Array(3).map(() => {})?

如果說 Array(3).fill({}) 不可行, 是因為三個空物件是對同一個物件的引用. 那麼我們就設法返回三個不同的空物件.

let studentRow = Array(3).map(() => {});
// > studentRow
// [ <3 empty items> ]
複製程式碼

結果很失望, 這個表示式就幹了兩件事, Array(3)map(() => {}). 所以問題很好排查.

let arr = Array(3);
// > arr
// [ <3 empty items> ]
複製程式碼

對於陣列中並不存在的單元, map() 也是束手無策.

我說: 肚裡要有貨?

肚裡沒貨, 我們就造一些. Array.prototype.fill() 又有出頭之日了.

let studentRow = Array(3).fill(undefined);
// > studentRow
// [ undefined, undefined, undefined ]
複製程式碼

警告:

  • 如若一個陣列沒有任何單元, 但它的 length 屬性中卻顯示有單元數量, 這樣奇特的資料結構會導致一些怪異的行為. 我們將包含至少一個 “空單元” 的陣列稱之為 “稀疏陣列”. undefined 單元非 “空單元”.

  • 永遠不要建立和使用空單元陣列.

箭頭函式中的 return


你以為 Array(3).fill(undefined).map(() => {}) 就完事了? 圖樣圖森破 ?

let studentRow = Array(3).fill(undefined).map(() => {});
// > studentRow
// [ undefined, undefined, undefined ]
複製程式碼

哦, 我知道了, 你沒有 return 啊

額, 這和 return 沒有關係. 不信你可以加一個試試? 其實, {} 在這裡被視作語法塊了, 沒有任何意義. 可恨就可恨在, 它和空物件長得一摸一樣. 既然這樣, 那我們就不用字面量定義一個空物件了.

let studentRow = Array(3).fill(undefined).map(() => Object.create(null));
studentRow[0].name = 'tony';
// > studentRow
// [ { name: 'tony' }, {}, {} ]
複製程式碼

這樣就達到初始化物件陣列的目的了. 可是, Array(3).fill(undefined).map(() => {}) 為什麼行不通, 如何補救?

規避問題在某種意義上不等於解決問題.

{...} 裡面的程式碼會被解析為一系列語句. {} 也因此不能達到我們預期的結果. 所以, 我們可以用 (...){} 包裝成表示式, 即 ({}).

let studentRow = Array(3).fill(undefined).map(() => ({}));
studentRow[0].name = 'tony';
// > studentRow
// [ { name: 'tony' }, {}, {} ]
複製程式碼

知識點:

  • 若函式體的表示式個數多於一個, 或者函式題包含非表示式語句的時候才需要用 {...} 包裹.

  • 如果只有一個表示式, 並且省略了 {...} 的話, 則附加一個隱式 return. 若在塊體內需要指定返回值, 則需要明確的 return.

  • 箭頭函式提供了簡練的語法, 但不是普通函式的替代品. 箭頭函式的主要設計目的是改變 this 的行為. 普通函式內的 this 是動態繫結, this 指向誰取決於呼叫者. 而箭頭函式裡的 this 是基於作用域的, 是可預測的.(可參考與作用域相關的閉包、記憶體洩漏、this).

令人絕望的Array.prototype.fill()


你以為結束了, 其實才剛剛開始

這是真正的開始, 沒看錯, 是的, 我們之前所做的可能都是無用功.

是的, IE 是魔鬼. 費盡了周折, 才發現一切都是徒勞. 難道就這麼放棄了?

'放棄'能吃麼? 能吃就吃了它, 啥? 不能吃?!? 提它作甚!!!

Array.prototype.fill() 方便之處就是能夠簡便填充陣列. 此法不行, 另尋他法.

Function.prototype.apply() 瞭解一下


Function.prototype.apply() 入參有兩個. 第一個引數是 函式方法 的呼叫者, 第二個引數是 函式方法 的入參(要區分入參和入參的不同). 函式方法 的入參可以是陣列也可以是類陣列. 我們的目的就是填充陣列, 所以我們要在類陣列上做文章. 就拿 console.log 做例子?. (直接複製我之前的部落格內容?).

function log_1(arg) {
console.log(arg)
}
log_1(1);
log_1(1,2,3);
// 1
// 1

// 改造下
function log_2() {
    const log = console.log;
    log.apply(null, arguments)
}
log_2(1);
log_2(1, 2, 3)
// 1
// 1 2 3
複製程式碼

這是 Function.prototype.apply() 使用的方法. 如果我們把 log_2 裡的 arguments 換成 {length: 3}

function log_2() {
    const log = console.log;
    log.apply(null, {length: 3})
}
log_2()
// undefined undefined undefined
複製程式碼

{length: 3}[undefined, undefined, undefined] 在傳入 apply(null;...) 後, 在引數的處理上, 最後的結果是一樣的. 那麼, Array(3).fill(undefined).map(() => ({})) 可改造成

let studentRow = Array.apply(null, {length: 3}).map(() => ({}));
studentRow[0].name = 'tony';
// > studentRow
// [ { name: 'tony' }, {}, {} ]
複製程式碼

在這裡 Array 作為普通函式呼叫, 以上等同於

let studentRow = Array(undefined, undefined, undefined);
// > studentRow
// [ undefined, undefined, undefined ]
複製程式碼

收尾


只是初始化一個空物件陣列, 結果整出這麼多么蛾子. 處理資料其實就那麼幾行程式碼. 大致長這模樣

function handleData(params) {
    const studentRow = Array.apply(null, {length: 3}).map(() => ({}));
    params.forEach(item => {
    studentRow[item.row-1][`row`] = item.row;
    studentRow[item.row-1][`col_${item.col}`] = item.name;
    })
    return studentRow;
}
複製程式碼

相關文章