比较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 已收录,有一线大厂面试完整考点、资料以及个人系列文章。

相关文章
相关标签/搜索