数据结构(二)之数组结构

如需转载, 请咨询作者, 并且注明出处.
有任何问题, 可以关注我的微博: coderwhy, 或者添加我的微信: 372623326

几乎所有的编程语言都原生支持数组类型,因为数组是最简单的内存数据结构。

数组通常情况下用于存储一系列同一种数据类型的值。

但在JavaScript里,也可以在数组中保存不同类型的值。但我们还是要遵守最佳实践,别这么做(大多数语言都没这个能力)。

一. 数组的基本使用

事实上, 如果你之前已经学习了JavaScript的基础知识, 那么数组这部分可以直接跳过.

因为在基础知识中, 是要求我们灵活使用数组的, 所以大部分人应该对这部分没有问题, 但是这里我还是花费一些篇章来带着大家一起学习一下数组.

因为数据结构中数组是其中很重要的一环, 直接跳过总觉得少点什么.

为什么使用数组?

  • 假如有这样一个需求:保存自己多个朋友的名字。可以这么做:

    // 保存班级中所有学生的名字
    var name1 = "Tom"
    var name2 = "Lily"
    var name3 = "Lucy"
    var name4 = "Lilei"
    var name5 = "Coderwhy"
    
  • 这不是一个好的解决方案

    • 因为假如班级有100个学生, 那么我们就需要有100个变量.
    • 100个变量的是非常不方便管理的, 而且当我们需要找到某一个学生时, 从100个变量中去搜索也是一个问题.
  • 很明显, 这种情况下, 我们通常会使用数组来解决:

    // 使用数组来保存学生名字
    var names = ["Tom", "Lily", "Lucy", "Lilei", "Coderwhy"]
    

创建和初始化数组

  • 用JavaScript声明、创建和初始化数组很简单,就像下面这样:

    // 创建和初始化数组
    var daysOfWeek = new Array()
    var daysOfWeek = new Array(7)
    var daysOfWeek = new Array('Sunday', 'Monday', 'Tuesday', 'Wednesday',
        'Thursday', 'Friday', 'Saturday')
    
  • 代码解析:

    • 使用new关键字,就能简单地声明并初始化一个数组
    • 用这种方式,还可以创建一个指定长度的数组.
    • 另外,也可以直接将数组元素作为参数传递给它的构造器
    • new创建数组并不是最好的方式。如果你想在JavaScript中创建一个数组,只用中括号([])的形式就行了
  • 使用中括号([])创建数组

    var daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday',
        'Thursday', 'Friday', 'Saturday'];
    

数组长度和遍历数组

  • 如果我们希望获取数组的长度, 有一个length属性

    // 获取数组的长度
    alert(daysOfWeek.length)
    
  • 也可以通过下标值来遍历数组:

    // 普通for方式遍历数组
    for (var i = 0; i < daysOfWeek.length; i++) {
        alert(daysOfWeek[i])
    }
    
    // 通过foreach遍历数组
    daysOfWeek.forEach(function (value) {
        alert(value)
    })
    
  • 我们来做一个练习:

    • 求菲波那切数列的前20个数字, 并且放在数组中.
    • 菲波那切数列数列第一个数字是1, 第二个数字也是1, 第三项是前两项的和
    // 求菲波那切数列的前20个数字
    var fibonacci = []
    fibonacci[0] = 1
    fibonacci[1] = 1
    
    for (var i = 2; i < 20; i++) {
        fibonacci[i] = fibonacci[i - 1] + fibonacci[i - 2]
    }
    
    alert(fibonacci)
    

二. 数组的常见操作

数组中常见的操作有: 添加元素、删除元素、修改元素、获取元素.

添加元素

  • JavaScript中, 进行我们上述的操作都比较简单: 因为语言本身都已经封装好了这些特定.

  • 假如我们有一个数组: numbers, 初始化0~9

    // 初始化一个数组
    var numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
  • 添加一个元素到数组的最后位置:

    // 添加一个元素到数组的最后位置
    // 方式一:
    numbers[numbers.length] = 10
    
    // 方式二:
    numbers.push(11)
    numbers.push(12, 13)
    
    alert(numbers)
    
  • 在数组首位插入一个元素:

    // 在数组首位插入一个元素
    for (var i = numbers.length; i > 0; i--) {
        numbers[i] = numbers[i-1]
    }
    numbers[0] = -1
    alert(numbers) // -1,0,1,2,3,4,5,6,7,8,9,10,11,12,13
    
  • 上面代码实现的原理是怎样的呢?

    img
  • 考虑上面代码实现的性能怎么呢?

    • 性能并不算非常高
    • 这也是数组和链表(后面我们会学习到)相对比的一个劣势: 在中间位置插入元素的效率比链表低.
  • 当然, 我们在数组首位插入数据可以直接使用unshift方法

    // 通过unshift在首位插入数据
    numbers.unshift(-2)
    numbers.unshift(-4, -3)
    alert(numbers) // -4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13
    

删除元素

  • 如果希望删除数组最后的元素, 可以使用pop()方法

    // 删除最后的元素
    numbers.pop()
    alert(numbers) // -4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,10,11,12
    
  • 如果我们希望移除的首位元素, 自己实现代码:

    // 删除首位的元素
    for (var i = 0; i < numbers.length; i++) {
        numbers[i] = numbers[i+1]
    }
    numbers.pop()
    alert(numbers)
    
  • 当然, 我们可以直接使用shift方法来实现:

    numbers.shift()
    alert(numbers)
    

任意位置

  • 任意位置?

    • 前面我们学习的主要是在数组的开头和结尾处添加和删除数据.
    • 那如果我们希望在数组的中间位置进行一些操作应该怎么办呢?
  • 一方面, 我们可以自己封装这样的函数, 但JS已经给我们提供了一个splice方法

  • 通过splice删除数据

    // 删除指定位置的几个元素
    numbers.splice(5, 3)
    alert(numbers) // -4,-3,-2,-1,0,4,5,6,7,8,9,10,11,12,13
    
  • 代码解析:

    • 上面的代码会删除索引为5, 6, 7位置的元素.
    • 第一个参数表示索引起始的位置为5(其实是第6个元素, 因为索引从0开始的), 删除3个元素.
  • 如果我们希望使用splice来插入数据呢?

    // 插入指定位置元素
    numbers.splice(5, 0, 3, 2, 1)
    alert(numbers) // -4,-3,-2,-1,0,3,2,1,4,5,6,7,8,9,10,11,12,13
    
  • 代码解析:

    • 上面的代码会从索引为5的位置开始插入数据. 其他数据依次向后位移.
    • 第一个参数依然是索引值为5(第六个位置)
    • 第二个参数为0时表示不是删除数据, 而是插入数据.
    • 后面紧跟的是在这个位置要插入的数据, 可以是其他类型, 比如"a", "b", "c".
  • 如果我们希望使用splice来修改数据呢?

    // 修改指定位置的元素
    numbers.splice(5, 3, "a", "b", "c")
    alert(numbers) // -4,-3,-2,-1,0,a,b,c,4,5,6,7,8,9,10,11,12,13
    
  • 代码解析:

    • 上面的代码会从索引5的位置开始修改数据, 修改多少个呢? 第二个参数来决定的.
    • 第一个参数依然是索引的位置为5(第六个位置)
    • 第二个参数是要将数组中多少个元素给替换掉, 我们这里是3个(也可以使用3个元素来替换2个, 可以自己尝试一下)
    • 后面跟着的就是要替换的元素.

三. 数组的其他操作

上面学习的是对数组的一些基本操作.

JavaScript中添加了很多方便操作数据的方法, 我们一些来简单回顾一下.

常见方法

  • 我们先对常见的方法简单来看一下

    方法名 方法描述
    concat 连接2个或更多数组,并返回结果
    every 对数组中的每一项运行给定函数,如果该函数对每一项都返回 true,则返回true, 否则返回false
    filter 对数组中的每一项运行给定函数,返回该函数会返回 true的项组成的数组
    forEach 对数组中的每一项运行给定函数。这个方法没有返回值
    join 将所有的数组元素连接成一个字符串
    indexOf 返回第一个与给定参数相等的数组元素的索引,没有找到则返回-1
    lastIndexOf 返回在数组中搜索到的与给定参数相等的元素的索引里最大的值
    map 对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组
    reverse 颠倒数组中元素的顺序,原先第一个元素现在变成最后一个,同样原先的最后一个元素变成了现在的第一个
    slice 传入索引值,将数组里对应索引范围内的元素作为新数组返回
    some 对数组中的每一项运行给定函数,如果任一项返回 true,则结果为true, 并且迭代结束
    sort 按照字母顺序对数组排序,支持传入指定排序方法的函数作为参数
    toString 将数组作为字符串返回
    valueOf toString类似,将数组作为字符串返回

数组合并

  • 数组的合并非常简单, 使用concat即可(也可以直接+进行合并)

    // 数组的合并
    var nums1 = [1, 2, 3]
    var nums2 = [100, 200, 300]
    var newNums = nums1.concat(nums2)
    alert(newNums) // 1,2,3,100,200,300
    
    newNums = nums1 + nums2
    alert(newNums) // 1,2,3,100,200,300
    

迭代方法

  • 为了方便操作数组, JS提供了很多迭代器方法, 我们来回顾一下

  • every()方法

    • every()方法是将数组中每一个元素传入到一个函数中, 该函数返回true/false.
    • 如果函数中每一个元素都返回true, 那么结果为true, 有一个为false, 那么结果为false
  • every()练习:

    • 判断一组元素中是否都包含某一个字符
    // 定义数组
    var names = ["abc", "cb", "mba", "dna"]
    
    // 判断数组的元素是否都包含a字符
    var flag = names.every(function (t) {
        return t.indexOf('a') != -1
    })
    alert(flag)
    
  • some()方法

    • some()方法是将数组中每一个元素传入到一个函数中, 该函数返回true/false
    • 但是和every不同的是, 一旦有一次函数返回了true, 那么迭代就会结束. 并且结果为true
  • some()练习

    // 定义数组
    var names = ["abc", "cb", "mba", "dna"]
    
    // 判断数组中是否包含有a字符的字符
    var flag = names.some(function (t) {
        alert(t)
        return t.indexOf("a") != -1
    })
    alert(flag)
    
  • forEach()方法

    • forEach()方法仅仅是一种快速迭代数组的方式而已.
    • 该方法不需要返回值
  • forEach的使用

    // 定义数组
    var names = ["abc", "cb", "mba", "dna"]
    
    // forEach的使用
    names.forEach(function (t) {
        alert(t)
    })
    
  • filter()方法

    • filter()方法是一种过滤的函数
    • 首先会遍历数组中每一个元素传入到函数中
    • 函数的结果返回true, 那么这个元素会被添加到最新的数组中, 返回false, 则忽略该元素.
    • 最终会形成一个新的数组, 该数组就是filter()方法的返回值
  • filter()的练习:

    // 定义数组
    var names = ["abc", "cb", "mba", "dna"]
    
    // 获取names中所有包含'a'字符的元素
    var newNames = names.filter(function (t) {
        return t.indexOf("a") != -1
    })
    alert(newNames)
    
  • map()方法

    • map()方法提供的是一种映射函数.
    • 首先会遍历数组中每一个元素传入到函数中.
    • 元素会经过函数中的指令进行各种变换, 生成新的元素, 并且将新的元素返回.
    • 最终会将返回的所有元素形成一个新的数组, 该数组就是map()方法的返回值
  • map()练习:

    // 定义数组
    var names = ["abc", "cb", "mba", "dna"]
    
    // 在names中所有的元素后面拼接-abc
    var newNames = names.map(function (t) {
        return t + "-abc"
    })
    alert(newNames)
    

reduce方法

  • 我们单独拿出reduce方法, 因为这个方法相对来说难理解一点

  • 首先, 我们来看这个方法需要的参数:

    arr.reduce(callback[, initialValue])
    
  • 参数

    • callback(一个在数组中每一项上调用的函数,接受四个函数:)
      • previousValue(上一次调用回调函数时的返回值,或者初始值)
      • currentValue(当前正在处理的数组元素)
      • currentIndex(当前正在处理的数组元素下标)
      • array(调用reduce()方法的数组)
    • initialValue(可选的初始值。作为第一次调用回调函数时传给previousValue的值)
  • 有些晦涩难懂, 我们直接看例子

    • 求一个数字中数字的累加和
  • 使用for实现:

    // 1.定义数组
    var numbers = [1, 2, 3, 4]
    
    // 2.for实现累加
    var total = 0
    for (var i = 0; i < numbers.length; i++) {
        total += numbers[i]
    }
    alert(total) // 10
    
  • 使用forEach简化for循环

    • 相对于for循环, forEach更符合我们的思维(遍历数组中的元素)
    // 3.使用forEach
    var total = 0
    numbers.forEach(function (t) {
        total += t
    })
    alert(total)
    
  • 使用reduce方法实现

    // 4.使用reduce方法
    var total = numbers.reduce(function (pre, cur) {
        return pre + cur
    })
    alert(total)
    
  • 代码解析:

    • pre中每次传入的参数是不固定的, 而是上次执行函数时的结果保存在了pre中
    • 第一次执行时, pre为0, cur为1
    • 第二次执行时, pre为1 (0+1, 上次函数执行的结果), cur为2
    • 第三次执行时, pre为3 (1+2, 上次函数执行的结果), cur为3
    • 第四次执行时, pre为6 (3+3, 上次函数执行的结果), cur为4
    • 当cur为4时, 数组中的元素遍历完了, 就直接将第四次的结果, 作为reduce函数的返回值进行返回.
  • 似乎和forEach比较没有太大的优势呢?

    • 通过这个代码你会发现, 你不需要在调用函数前先定义一个变量, 只需要一个变量来接收方法最终的参数即可.
    • 但是这就是优势吗? 不是, 优势在于reduce方法有返回值, 而forEach没有.
    • 这算什么优势? 如果reduce方法有返回值, 那么reduce方法本身就可以作为参数直接传递给另外一个需要reduce返回值的作为参数的函数. 而forEach中你只能先将每次函数的结果保存在一个变量, 最后再将变量传入到参数中.
    • 没错, 这就是最近非常流行的函数式编程. 也是为了几乎每个可以使用函数式编程的语言都有reduce这个方法的原因.
    • 关于函数式编程, 不再本次课程的讨论之中, 只是看到了这个函数, 给大家延伸了一下而已.(后面有机会和大家分享函数式编程)
  • initialValue还需要讲吗?

    • 其实就是第一次执行reduce中的函数时, pre的值.
    • 因为默认pre第一次执行时为0.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容

  • 第一章: JS简介 从当初简单的语言,变成了现在能够处理复杂计算和交互,拥有闭包、匿名函数, 甚至元编程等...
    LaBaby_阅读 1,640评论 0 6
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,212评论 0 4
  • 经常会听到一些抱怨的声音,比如说最近的运势不太好,社会大环境不好等这样的声音,找的只是客观原因,而不去找自身的...
    Jessica_ff17阅读 233评论 1 0
  • 我希望改善与大宝的关系,让二宝的出生带给她的伤害降到最低。 现在我家大宝晨妞七年级,二宝堃妞二个月。我现在跟大宝的...
    我是张老师阅读 319评论 0 1
  • 谁说男的不能矫情,不能抱怨,抱怨就像娘们。娘们都喜欢抱怨矫情,岂不是满城皆怨妇。我只能通过打字来搞这些鬼事,以保...
    亦凡_阅读 293评论 1 1