比較JavaScript中的數據結構(數組與對象)

做者:Vivek Bisht
譯者:前端小智
來源:blog
移動端閱讀: https://mp.weixin.qq.com/s/nbYsKonWtLNK95jMd4dY-A

有夢想,有乾貨,微信搜索 【大遷世界】 關注這個在凌晨還在刷碗的刷碗智。javascript

本文 GitHub https://github.com/qq449245884/xiaozhi 已收錄,有一線大廠面試完整考點、資料以及個人系列文章。前端

在編程中,若是你想繼續深刻,數據結構是咱們必需要懂的一塊, 學習/理解數據結構的動機可能會有所不一樣,一方面多是爲了面試,一方面可能單單是爲了提升本身的技能或者是項目須要。不管動機是什麼,若是不知道什麼是數組結構及什麼時候使用應用字們,那學數據結構是一項繁瑣且無趣的過程 😵java

這篇文章討論了何時使用它們。在本文中,咱們將學習數組和對象。咱們將嘗試經過使用Big O notation來理解什麼時候選擇一種數據結構。git

Big O notation 大零符號通常用於描述算法的複雜程度,好比執行的時間或佔用內存(磁盤)的空間等,特指最壞時的情形。

數組

數組是使用最普遍的數據結構之一。 數組中的數據以有序的方式進行結構化,即數組中的第一個元素存儲在索引0中,第二個元素存儲在索引1中,依此類推。 JavaScript爲咱們提供了一些內置的數據結構,數組就是其中之一 👌github

在JavaScript中,定義數組最簡單的方法是:面試

let arr = []

上面的代碼行建立了一個動態數組(長度未知),爲了瞭解如何將數組的元素存儲在內存中,咱們來看一個示例:算法

let arr = ['John', 'Lily', 'William', 'Cindy']

在上面的示例中,咱們建立一個包含一些人名的數組。 內存中的名稱按如下方式存儲:編程

clipboard.png

爲了理解數組是如何工做的,咱們須要執行一些操做:數組

添加元素:

在JavaScript數組中,咱們有不一樣方式在數組結尾,開關以及特定索引處添加元素。微信

在數組的末尾添加一個元素:

JavaScript 中的數組有一個默認屬性 length,它表示數組的長度。除了length屬性外,JS還提供了 push() 方法。 使用這個方法,咱們能夠直接在最後添加一個元素。

arr.push('Jake')

clipboard.png

那麼這個命令的複雜度是多少呢?咱們知道,在默認狀況下,JS提供了length屬性,push()至關於使用如下命令:

arr[arr.length - 1] = 'Jake'

由於咱們老是能夠訪問數組的長度屬性,因此不管數組有多大,在末尾添加一個元素的複雜度老是O(1) 👏。

在數組的開頭添加一個元素:

對於此操做,JavaScript提供了一個稱爲unshift()的默認方法,此方法將元素添加到數組的開頭。

let arr = ['John', 'Lily', 'William', 'Cindy']
arr.unshift('Robert')
console.log(arr) // [ 'Robert', 'John', 'Lily', 'William', 'Cindy' ]

unshift方法複雜度好像和push方法的複雜度同樣:O(1),由於咱們只是在前面添加一個元素。 事實並不是如此,讓咱們看一下使用unshift方法時會發生什麼:

clipboard.png

在上圖中,當咱們使用unshift方法時,全部元素的索引應該增長1。這裏咱們的數組個數比較少,看不出存在的問題。想象一下使用一個至關長的數組,而後,使用unshift這樣的方法會致使延遲,由於咱們必須移動數組中每一個元素的索引。所以,unshift操做的複雜度爲O(n) 😋。

若是要處理較大長度的數組,請明智地使用unshift方法。在特定索引處添加元素,咱們能夠 splice() 方法,它的語法以下:

splice(startingIndex, deleteCount, elementToBeInserted)

由於咱們要添加一個元素,因此deleteCount將爲0。例如, 咱們想要在數組索引爲2的地方新加一個元素,能夠這麼用:

let arr = ['John', 'Lily', 'William', 'Cindy']
arr.splice(2, 0, 'Janice')
console.log(arr)
// [ 'John', 'Lily', 'Janice', 'William', 'Cindy' ]

你以爲這個操做的複雜性是多少?在上面的操做中,咱們在索引2處添加了元素,所以,在索引2以後的全部後續元素都必須增長或移動1(包括以前在索引2處的元素)。

clipboard.png

能夠觀察到,咱們不是在移動或遞增全部元素的索引,而是在索引2以後遞增元素的索引。這是否意味着該操做的複雜度爲 `O(n/2)? 不是 😮。 根據Big O規則,常量能夠從複雜性中刪除,並且,咱們應該考慮最壞的狀況。 所以,該操做的複雜度爲O(n) 😳。

刪除元素:

就像添加元素同樣,刪除元素能夠在不一樣的位置完成,在末尾、開始和特定索引處。

在數組的末尾刪除一個元素:

push( )同樣,JavaScript提供了一個默認方法pop(),用於刪除/刪除數組末尾的元素。

let arr = ['Janice', 'Gillian', 'Harvey', 'Tom']
arr.pop()
console.log(arr)
// [ 'Janice', 'Gillian', 'Harvey' ]

arr.pop()
console.log(arr)
// [ 'Janice', 'Gillian' ]

該操做的複雜度爲O(1)。由於,不管數組有多大,刪除最後一個元素都不須要改變數組中任何元素的索引。

在數組的開頭刪除一個元素:

JavaScript 提供了一個默認方法shift() 的默認方法,此方法刪除數組的第一個元素。

let arr = ['John', 'Lily','William','Cindy']
arr.shift()
console.log(arr) // ['Lily','William','Cindy']
arr.shift()
console.log(arr);// ['William','Cindy']

clipboard.png

從上面咱們很容易能夠看出 shift()操做的複雜度爲O(n) ,由於刪除第一個元素後,咱們必須將全部元素的索引移位或減量1

在特定索引處刪除:

對於此操做,咱們再次使用splice()方法,不過這一次,咱們只使用前兩個參數,由於咱們不打算在該索引處添加新元素。

let arr = ['Apple', 'Orange', 'Pear', 'Banana','Watermelon']
arr.splice(2,1)
console.log(arr) // ['Apple', 'Orange', 'Banana','Watermelon']

與用splice添加元素操做相似,在此操做中,咱們將遞減或移動索引2以後的元素索引,因此複雜度是O(n)

查找元素:

查找只是訪問數組的一個元素,咱們能夠經過使用方括號符號(例如: arr[4])來訪問數組的元素。

你認爲這個操做的複雜性是什麼? 咱們經過一個例子來演示一下:

let fruits = ['Apple', 'Orange', 'Pear']

clipboard.png

前面咱們已經看到,數組的全部元素都按順序存儲,而且始終分組在一塊兒。 所以,若是執行fruits[1],它將告訴計算機找到名爲fruits的數組並獲取第二個元素(數組從索引0開始)。

因爲它們是按順序存儲的,所以計算機沒必要查看整個內存便可找到該元素,由於全部元素按順序分組在一塊兒,所以它能夠直接在fruits數組內部查看。 所以,數組中的查找操做的複雜度爲 O(1)

咱們已經完成了對數組的基本操做,咱們先來小結一下何時可使用數組:

當你要執行像push()(在末尾添加元素)和pop()(從末尾刪除元素)這樣的操做時,數組是合適的,由於這些操做的複雜度是O(1)

除此以外,查找操做能夠在數組中很是快地執行。

使用數組時,執行諸如在特定索引處或在開頭添加/刪除元素之類的操做可能會很是慢,由於它們的複雜度爲O(n)

對象

像數組同樣,對象也是最經常使用的數據結構之一。 對象是一種哈希表,容許咱們存儲鍵值對,而不是像在數組中看到的那樣將值存儲在編號索引處。

定義對象的最簡單方法是:

let obj1 = {}

事例:

let student = {
    name: 'Vivek',
    age: 13,
    class: 8
}

來看一下上面的對象是如何存儲在內存中的:

clipboard.png

能夠看到,對象的鍵-值對是隨機存儲的,不像數組中全部元素都存儲在一塊兒。這也是數組與對象的主要區別,在對象中,鍵-值對隨機存儲在內存中。

咱們還看到有一個哈希函數(hash function)。 那麼這個哈希函數作什麼呢? 哈希函數從對象中獲取每一個鍵,並生成一個哈希值,而後將此哈希值轉換爲地址空間,在該地址空間中存儲鍵值對。

例如,若是咱們向學生對象添加如下鍵值對:

student.rollNumber = 322

rollNumber鍵經過哈希函數,而後轉換爲存儲鍵和值的地址空間。如今咱們已經對對象如何存儲在內存有了基本的瞭解,讓咱們來執行一些操做。

添加

對於對象,咱們沒有單獨的方法將元素添加到前面或後面,由於全部的鍵-值對都是隨機存儲的。只有一個操做是向對象添加一個新的鍵值對。

事例:

student.parentName = 'Narendra Singh Bisht'

clipboard.png

從上圖中咱們能夠得出結論,這個操做的複雜性老是O(1),由於咱們不須要改變任何索引或操做對象自己,咱們能夠直接添加一個鍵-值對,它被存儲在一個隨機的地址空間。

刪除

與添加元素同樣,對象的刪除操做很是簡單,複雜度爲O(1)。由於,咱們沒必要在刪除時更改或操做對象。

delete student.parentName

查找

查找的複雜度O(1) ,由於在這裏,咱們也只是藉助鍵來訪問值。訪問對象中的值的一種方法:

student.class

在對象中添加,刪除和查找的複雜度爲O(1)???那麼咱們能夠得出結論,咱們應該每次都使用對象而不是數組嗎? 答案是不。 儘管對象很棒,可是在使用對象時須要考慮一些小的狀況,就是哈希碰撞(Hash Collisions)。 在使用對象時,並不是始終應處理此狀況,但瞭解該狀況有助於咱們更好地理解對象。

那麼什麼是哈希碰撞?

當咱們定義一個對象時,咱們的計算機會在內存中爲該對象分配一些空間。 咱們須要記住,咱們內存中的空間是有限的,所以有可能兩個或更多鍵值對可能具備相同的地址空間,這種狀況稱爲哈希碰撞。 爲了更好地理解它,咱們看一個例子:

假設爲下面的對象分配了5塊空間

clipboard.png

咱們觀察到兩個鍵值對存儲在相同的地址空間中。 怎麼會這樣?當哈希函數返回一個哈希值,該哈希值轉換爲多個鍵的相同地址空間時,就會發生這種狀況。 所以,多個 key 被映射到相同的地址空間。 因爲哈希碰撞,添加和訪問對象值的複雜度爲O(n) ,由於要訪問特定值,咱們可能必須遍歷各類鍵值對。

哈希碰撞並非咱們每次使用對象時都須要處理的東西。 這只是一個特殊的狀況,該狀況也說明了對象不是完美的數據結構。

除了*哈希碰撞,使用對象時還必須注意另外一種狀況。 JS 爲咱們提供了一個內置的keys()方法,用於遍歷對象的鍵。

咱們能夠將此方法應用於任何對象,例如:object1.keys()keys()方法遍歷對象並返回全部鍵。 儘管此方法看起來很簡單,但咱們須要瞭解對象中的鍵值對是隨機存儲在內存中的,所以,遍歷對象的過程變得較慢,這與遍歷按順序將它們分組在一塊兒的數組不一樣。

總結一下,當咱們想執行諸如添加,刪除和訪問元素之類的操做時,可使用對象,可是在使用對象時,咱們須要謹慎地遍歷對象,由於這可能很耗時。 除了進行遍歷外,咱們還應該理解,有時因爲哈希碰撞,訪問對象操做的複雜度可能會變爲O(n)


代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug

原文:https://blog.soshace.com/comparing-data-structures-in-javascript-arrays-vs-objects/

交流

有夢想,有乾貨,微信搜索 【大遷世界】 關注這個在凌晨還在刷碗的刷碗智。

本文 GitHub https://github.com/qq449245884/xiaozhi 已收錄,有一線大廠面試完整考點、資料以及個人系列文章。

相關文章
相關標籤/搜索