做者: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']
在上面的示例中,咱們建立一個包含一些人名的數組。 內存中的名稱按如下方式存儲:編程
爲了理解數組是如何工做的,咱們須要執行一些操做:數組
在JavaScript數組中,咱們有不一樣方式在數組結尾,開關以及特定索引處添加元素。微信
在數組的末尾添加一個元素:
JavaScript 中的數組有一個默認屬性 length,它表示數組的長度。除了length屬性外,JS還提供了 push() 方法。 使用這個方法,咱們能夠直接在最後添加一個元素。
arr.push('Jake')
那麼這個命令的複雜度是多少呢?咱們知道,在默認狀況下,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
方法時會發生什麼:
在上圖中,當咱們使用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處的元素)。
能夠觀察到,咱們不是在移動或遞增全部元素的索引,而是在索引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']
從上面咱們很容易能夠看出 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']
前面咱們已經看到,數組的全部元素都按順序存儲,而且始終分組在一塊兒。 所以,若是執行fruits[1],它將告訴計算機找到名爲fruits
的數組並獲取第二個元素(數組從索引0
開始)。
因爲它們是按順序存儲的,所以計算機沒必要查看整個內存便可找到該元素,由於全部元素按順序分組在一塊兒,所以它能夠直接在fruits
數組內部查看。 所以,數組中的查找操做的複雜度爲 O(1)。
咱們已經完成了對數組的基本操做,咱們先來小結一下何時可使用數組:
當你要執行像push()
(在末尾添加元素)和pop()
(從末尾刪除元素)這樣的操做時,數組是合適的,由於這些操做的複雜度是O(1)
。
除此以外,查找操做能夠在數組中很是快地執行。
使用數組時,執行諸如在特定索引處或在開頭添加/刪除元素之類的操做可能會很是慢,由於它們的複雜度爲O(n)。
像數組同樣,對象也是最經常使用的數據結構之一。 對象是一種哈希表,容許咱們存儲鍵值對,而不是像在數組中看到的那樣將值存儲在編號索引處。
定義對象的最簡單方法是:
let obj1 = {}
事例:
let student = { name: 'Vivek', age: 13, class: 8 }
來看一下上面的對象是如何存儲在內存中的:
能夠看到,對象的鍵-值對是隨機存儲的,不像數組中全部元素都存儲在一塊兒。這也是數組與對象的主要區別,在對象中,鍵-值對隨機存儲在內存中。
咱們還看到有一個哈希函數(hash function)。 那麼這個哈希函數作什麼呢? 哈希函數從對象中獲取每一個鍵,並生成一個哈希值,而後將此哈希值轉換爲地址空間
,在該地址空間中存儲鍵值對。
例如,若是咱們向學生對象添加如下鍵值對:
student.rollNumber = 322
rollNumber
鍵經過哈希函數,而後轉換爲存儲鍵和值的地址空間。如今咱們已經對對象如何存儲在內存有了基本的瞭解,讓咱們來執行一些操做。
對於對象,咱們沒有單獨的方法將元素添加到前面或後面,由於全部的鍵-值對都是隨機存儲的。只有一個操做是向對象添加一個新的鍵值對。
事例:
student.parentName = 'Narendra Singh Bisht'
從上圖中咱們能夠得出結論,這個操做的複雜性老是O(1),由於咱們不須要改變任何索引或操做對象自己,咱們能夠直接添加一個鍵-值對,它被存儲在一個隨機的地址空間。
與添加元素同樣,對象的刪除操做很是簡單,複雜度爲O(1)。由於,咱們沒必要在刪除時更改或操做對象。
delete student.parentName
查找的複雜度O(1) ,由於在這裏,咱們也只是藉助鍵來訪問值。訪問對象中的值的一種方法:
student.class
在對象中添加,刪除和查找的複雜度爲O(1)???那麼咱們能夠得出結論,咱們應該每次都使用對象而不是數組嗎? 答案是不。 儘管對象很棒,可是在使用對象時須要考慮一些小的狀況,就是哈希碰撞(Hash Collisions)。 在使用對象時,並不是始終應處理此狀況,但瞭解該狀況有助於咱們更好地理解對象。
那麼什麼是哈希碰撞?
當咱們定義一個對象時,咱們的計算機會在內存中爲該對象分配一些空間。 咱們須要記住,咱們內存中的空間是有限的,所以有可能兩個或更多鍵值對可能具備相同的地址空間,這種狀況稱爲哈希碰撞
。 爲了更好地理解它,咱們看一個例子:
假設爲下面的對象分配了5塊空間
咱們觀察到兩個鍵值對存儲在相同的地址空間中。 怎麼會這樣?當哈希函數返回一個哈希值,該哈希值轉換爲多個鍵的相同地址空間時,就會發生這種狀況。 所以,多個 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 已收錄,有一線大廠面試完整考點、資料以及個人系列文章。