JavaScript 中的稀疏數(shù)組

前言

最近有空在看一本關(guān)于 JS 數(shù)據(jù)結(jié)構(gòu)和算法的書,里面有提到數(shù)組讲衫,卻對(duì)數(shù)組的基本概念輕輕帶過(guò)缕棵,雖然用了 JS 很久但是一直忙于需求業(yè)務(wù)的實(shí)現(xiàn)從未停下好好回視一下這個(gè) 既熟悉又陌生的朋友,于是查閱了一些資料焦人,尤其是密集數(shù)組和稀疏數(shù)組的區(qū)別挥吵,意猶未盡之下,寫了這篇文章花椭,以便更好地幫助理解書中的要點(diǎn)忽匈,稍顯淺顯,也有不足望各位提點(diǎn)矿辽。

什么是稀疏數(shù)組丹允?

通常編程語(yǔ)言中(C郭厌、JAVA等)數(shù)組都是預(yù)先設(shè)定好長(zhǎng)度的,他們的內(nèi)存占用是固定的雕蔽,內(nèi)存地址是連續(xù)不間斷折柠、緊密相連的,我們稱之為密集數(shù)組批狐,好比 JS 中類型化數(shù)組(TypedArray)的 ArrayBuffer扇售,但我們這里著重要說(shuō)的是通過(guò) Array 創(chuàng)建的數(shù)組,它其實(shí)是個(gè)對(duì)象嚣艇,當(dāng)我們通過(guò) new Array() 創(chuàng)建一個(gè)數(shù)組時(shí)承冰,它只是一個(gè)帶有 length 屬性的數(shù)組對(duì)象,我們可以像對(duì)象一樣去操作它的屬性:

let array = new Array(10)
// 下標(biāo)可以是任何的字符串食零,就像操作 Object 一樣
array.name = 'This is an Array.'
array['name'] = 'This is an Array.' // 等效 array.name

雖然可以像操作 Object 那樣為數(shù)組對(duì)象添加屬性困乒,但是真正計(jì)入數(shù)組元素的只能是以整數(shù)類型的字符作為下標(biāo)去映射值的元素,同時(shí)會(huì)影響其長(zhǎng)度屬性 length贰谣,我們接上段代碼繼續(xù):

// 雖然之前賦上了一個(gè)新的屬性 name娜搂,但是其長(zhǎng)度仍然輸出10,而非11
console.log(array.length) // 10

// 用整數(shù)類型下標(biāo)定義一個(gè)數(shù)組元素才會(huì)計(jì)入數(shù)組長(zhǎng)度
array['0'] = 'first' // 下標(biāo)其實(shí)都是字符串
array[11] = 'eleventh' // 看起來(lái)這個(gè)下標(biāo)是一個(gè)數(shù)字吱抚,但是 JS 會(huì)自動(dòng)把它轉(zhuǎn)為字符
console.log(array.length) // 12

長(zhǎng)度屬性是可以手動(dòng)變更的:

array.length = 100
console.log(array.length) // 100

數(shù)組對(duì)象本身可能不占用太多內(nèi)存百宇,它只是包含了映射關(guān)系和長(zhǎng)度,真正占用內(nèi)存的是元素所映射的目標(biāo)频伤,也就是“鍵值”中的“值”恳谎。在V8引擎里芝此,JS 的數(shù)組得到進(jìn)一步的優(yōu)化憋肖,其中有個(gè)概念叫 Holey(有孔洞的),也就是那些沒有被指明映射關(guān)系的空間婚苹,他們不占用內(nèi)存岸更。如上看到,作為一門動(dòng)態(tài)語(yǔ)言膊升,我們隨時(shí)可以向這個(gè)數(shù)組對(duì)象里添加怎炊、修改元素映射,也能隨時(shí)改變其長(zhǎng)度 length廓译。自然地评肆,其元素所占的內(nèi)存地址也就無(wú)所謂固定連續(xù)了,我們稱這種數(shù)組為稀疏數(shù)組(Sparse Array)非区。

創(chuàng)建帶有孔洞的稀疏數(shù)組

  1. 使用 Array 構(gòu)造函數(shù):
let sparse = new Array(100)
  1. 通過(guò)字面量:
let sparse = []
// 用一個(gè)超過(guò)原始長(zhǎng)度的下標(biāo)為元素賦值瓜挽,也就為該元素創(chuàng)建了一個(gè)映射關(guān)系,同時(shí)改變了該數(shù)組的長(zhǎng)度
sparse[99] = 'last'
console.log(sparse.length) // 100

// 用對(duì)象字面量書寫數(shù)組時(shí)征绸,允許元素留空
sparse = [, , ,] // 這就創(chuàng)建了長(zhǎng)度為3的空洞的數(shù)組
console.log(sparse.length) // 3
  1. 手動(dòng)修改一個(gè)大于原數(shù)組長(zhǎng)度的 length 值:
let sparse = ['red', 'green', 'blue'] // 三個(gè)元素的數(shù)組
sparse.length = 100 // 此時(shí)長(zhǎng)度已是100久橙,但有效元素仍然只有3個(gè)

刪除元素的映射

從數(shù)組中刪除某個(gè)元素可以使用 pop俄占、shiftsplice 方法淆衷,那如何解除元素與某個(gè)對(duì)象的映射關(guān)系呢缸榄?我們可以像操作對(duì)象一樣用 delete,它不會(huì)刪除映射目標(biāo)祝拯,僅僅是將元素和目標(biāo)對(duì)象的關(guān)聯(lián)斷開甚带,從而形成一個(gè)孔洞,所以也不會(huì)改變這個(gè)數(shù)組的 length佳头。

let one = '壹'
let array = []

array[0] = one // 將數(shù)組的第一個(gè)元素指到對(duì)象 one欲低,長(zhǎng)度變?yōu)?1
console.log(array, array.length) // ['壹'], 1

delete array[0] // 刪除數(shù)組第一個(gè)元素的映射關(guān)系
console.log(array) // [空]
console.log(array.length) // 因?yàn)橹皇莿h除了元素的映射,長(zhǎng)度并沒有改變畜晰,仍然輸出 1
console.log(one) // one 的值并不會(huì)刪除砾莱,仍然保留,輸出 '壹'

現(xiàn)象

我們說(shuō)到凄鼻,JS 的數(shù)組本質(zhì)上是對(duì)象——由整數(shù)字符作為下標(biāo)與目標(biāo)值構(gòu)成映射關(guān)系的自帶長(zhǎng)度屬性的對(duì)象腊瑟,長(zhǎng)度可以大于有效(已創(chuàng)建映射關(guān)系的)元素的個(gè)數(shù)。從映射狀態(tài)來(lái)看块蚌,完全映射的數(shù)組有著和傳統(tǒng)密集數(shù)組類似的表現(xiàn)闰非,而不完全映射的數(shù)組在生產(chǎn)中會(huì)有些特別,但又在情理之中。

  1. 訪問沒有映射關(guān)系的數(shù)組元素時(shí)腔剂,相當(dāng)于一個(gè)申明了卻沒有定義值的變量痴脾,所以會(huì)輸出 undefined
let sparse = new Array(10)
console.log(sparse[0]) // undefined
  1. 不完全映射的數(shù)組,在用 map()辆毡、forEach() 等方法做遍歷時(shí),它們只會(huì)遍歷已有映射關(guān)系的元素:
// 此時(shí)數(shù)組元素的映射關(guān)系一個(gè)都沒有創(chuàng)建甜害,所以 forEach 不會(huì)有任何輸出
sparse.forEach((value, i) => {
  console.log(i, value)
}) // nothing

// 根據(jù)下標(biāo)為最后一個(gè)元素賦值
sparse[sparse.length - 1] = 'tenth'

// 只會(huì)輸出已建立映射的元素 sparse[sparse.length-1] 的值 'tenth'
sparse.forEach((value, i) => {
  console.log(i, value)
}) // 9, tenth
  1. 使用 for 語(yǔ)句是用數(shù)組的 length 值作為循環(huán)依據(jù)舶掖,它不會(huì)主動(dòng)判斷當(dāng)前位置是否有映射值,所以當(dāng)循環(huán)體試圖通過(guò)下標(biāo)訪問沒有映射關(guān)系的位置時(shí)尔店,會(huì)輸出 undefined
for( let i = 0; i < sparse.length; i += 1){
  console.log(i, sparse[i])
}
  1. 映射完全的數(shù)組眨攘,可以被所有常用數(shù)組方法遍歷:
// 一個(gè)完全映射的數(shù)組,這里的 undefined 是主動(dòng)賦值的有效映射
let array = ['1', 'day', 'white', 'Jake', undefined, null, 0]

// 輸出每個(gè)元素嚣州,包括null鲫售、undefined、0
array.forEach((value, i) => {
  console.log(i, value)
})
  1. 在做 some()该肴、every() 等操作時(shí)情竹,不完全映射的數(shù)組的表現(xiàn)是特殊的,但也在情理之中沙庐,這取決于這些方法的設(shè)計(jì)鲤妥,例如 some()佳吞,即便長(zhǎng)度大于0,但因?yàn)槠渲袥]有任何建立映射的元素棉安,所以底扳,相當(dāng)于給一個(gè)空數(shù)組([])做操作:
let sparse = new Array(10)

// 由于還沒有建立映射關(guān)系,所以 some 的回調(diào)也沒有觸發(fā)贡耽,于是得到的結(jié)果依然是 false
console.log(sparse.some(item => !item)) // false

// 在所有元素都被賦值后衷模,用同樣的回調(diào),some 的結(jié)果發(fā)生了變化
sparse.fill(false) // 賦值
console.log(sparse.some(item => !item)) // true
  1. 當(dāng)把長(zhǎng)度設(shè)成小于實(shí)際元素個(gè)數(shù)的值時(shí)蒲赂,會(huì)把超出長(zhǎng)度的元素從當(dāng)前數(shù)組中剔除:
let array = ['one', 'two', 'three']

array.length = 1
console.log(array) // ['one']

稀疏數(shù)組的快速映射(強(qiáng)制創(chuàng)建映射關(guān)系)

只是讓數(shù)組中的映射元素個(gè)數(shù)與長(zhǎng)度屬性相同阱冶,并不能改變其稀疏的特性:

let sparse = new Array(5)

// Array.apply
let array1 = Array.apply(null, sparse)
// Array.from方法
let array2 = Array.from(sparse)
// 解構(gòu)
let array3 = new Array(...sparse)
let array4 = [...sparse]

// 把所有元素映射為 undefined 了
array1.forEach((item, i) => {
  console.log(i, item) // 輸出 undefined × 5
})
……

總結(jié)

以上是對(duì) JS Array 數(shù)組的粗淺認(rèn)知,在 JS 這門動(dòng)態(tài)語(yǔ)言里滥嘴,通過(guò) Array 所創(chuàng)建的數(shù)組無(wú)所謂疏密木蹬,因?yàn)樗鼈儽举|(zhì)上還是對(duì)象。在實(shí)際生產(chǎn)時(shí)若皱,對(duì)于數(shù)據(jù)集合的操作應(yīng)當(dāng)盡量保持映射完全镊叁,避免不合預(yù)期的意外。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末走触,一起剝皮案震驚了整個(gè)濱河市晦譬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌互广,老刑警劉巖敛腌,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異惫皱,居然都是意外死亡像樊,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門逸吵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)凶硅,“玉大人,你說(shuō)我怎么就攤上這事扫皱。” “怎么了捷绑?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵韩脑,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我粹污,道長(zhǎng)段多,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任壮吩,我火速辦了婚禮进苍,結(jié)果婚禮上加缘,老公的妹妹穿的比我還像新娘。我一直安慰自己觉啊,他們只是感情好拣宏,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著杠人,像睡著了一般勋乾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嗡善,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天辑莫,我揣著相機(jī)與錄音,去河邊找鬼罩引。 笑死各吨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的袁铐。 我是一名探鬼主播绅你,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼昭躺!你這毒婦竟也來(lái)了忌锯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤领炫,失蹤者是張志新(化名)和其女友劉穎偶垮,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體帝洪,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡似舵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了葱峡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砚哗。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖砰奕,靈堂內(nèi)的尸體忽然破棺而出蛛芥,到底是詐尸還是另有隱情,我是刑警寧澤军援,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布仅淑,位于F島的核電站,受9級(jí)特大地震影響胸哥,放射性物質(zhì)發(fā)生泄漏涯竟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望庐船。 院中可真熱鬧银酬,春花似錦、人聲如沸筐钟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)盗棵。三九已至壮韭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纹因,已是汗流浹背喷屋。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瞭恰,地道東北人屯曹。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像惊畏,于是被迫代替她去往敵國(guó)和親恶耽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容