「JS系列之基础整理」扫盲(2)-数组专题

1,318 阅读11分钟

哈喽,大家好!这期呢,针对数组做一个专题,上期主要是理论基础,这期会多一些案例分析,希望这些小的案例能够更好的帮助大家理解记忆,同时自己肯定会有许多不足之处,还请各位不吝赐言,我们一起进步!另外,因为是专题期,本期中可能会有一些深入的探讨,也会涉及一些es6的知识,如果刚开始接触编程,可能会有一些吃力,不要放弃哦!废话不多说,我是阿树,让我们一起开启本期的扫盲吧!

数组,顾名思义就是数据组合,也就是一组数据的集合,那我们知道集合中的个体被称作元素,这里的数据也就是元素,又因为js是弱类型语言,所以在不加约束的情况下一个数组内的元素数据类型可以是任意的。在ts中,一般我们会对数组中的数据类型做统一的约束处理,这时候我们就不能任意存放了,对于ts相关的知识,我们后续整理。

一、数组的创建方式

1.1 利用new关键词创建数组

使用 new 运算符调用 Array() 类型函数时,可以构造一个新数组。

var newArr = new Array();
console.log(newArr) // [ ]
// 在这里我们创建了一个空数组,里面没有任何的是数据

var newArr1 = new Array(2)
console.log(newArr1) // [empty*2]
// 这里我们创建了一个长度为2的数组,里面依旧没有数据,相当于我们平时在教室里占位置,人还没来
// 也就是说,当我们只传入一个 数值型的参数的时候相当于声明了这个数组的长度,并没有存放数据

var newArr2 = new Array(2, 3)
console.log(newArr2) // [2, 3]
// 这里我们创建了含两个元素的数组。每个参数指定一个元素的值,值得类型没有限制。
// 参数的顺序也是数组元素的顺序,数组的 length 属性值等于所传递参数的个数

1.2 利用数组字面量创建数组

字面量表示如何表达这个值,一般除去表达式z,给变量赋值时,等号右边都可以认为是字面量。

var newArr = [];  //创建了一个空数组
var a = [1, "0", [1,0]];  // 创建了一个包含具体元素的数组
// 通常来说我们都在使用这种方式去创建一个数组,更高效一些。

二、访问数组

2.1 数组索引

数组是复合型数据,数组名称是一个指向数组的引用型变量,因此数组属于引用型对象。

数组的下标从 0 开始,我们可以通过索引来访问、设置、修改对应的数组元素,我们可以通过数组名[索引] 的形式获取相应元素。数组下标是非负整数型表达式,或者是字符型数字,不可以为其他类型的值或表达式

2.1.1. 正常索引访问

var testArr = ['詹姆斯', '科比', '韦德']
console.log(testArr[0]) // 詹姆斯
console.log(testArr[1]) // 科比
console.log(testArr[2]) // 韦德

2.1.2. 表达式索引设置数组元素

let testArr1 = new Array();
for (let i = 0; i < 10; i ++) {
    testArr1[i ++] = ++ i;
}
console.log(testArr1);

数组值设置过程(i)

  • i ++ 后置自增 也就是 索引为 0 后 自加1 此时 i = 1
  • ++ i 前置自增 此时 i = 2 也就是 testArr1[0] = 2
  • 循环体完成 i++ 自增 1 也就是 此时 索引 为 3 以此类推进行循环赋值
  • 最终结果是 [2, empty × 2, 5, empty × 2, 8, empty × 2, 11] 这是因为表达式索引为0,3,6,9 只有这四个索引下标有值,其余位置为空

2.1.3 案例 交换两个变量值 方法对比

方法一:设置中间量转接

let a = 10, b = 20
let temp = a
a = b;
b = temp
console.log(a, b) // 20 , 10
// 这种方法比较直观,关键点就是中间量的设计,这里不再鳌述。

方法二: 通过匿名数组转换

let c = 10, d = 20;
c = [d, d = c][0];
console.log(c, d)
// 这种方式可读性相对较差,容易让人看不懂,(但是写的都让别人看懂了如何体现我们的优秀呢,哈哈)

梳理一下转换的过程

  • 将d的值赋给匿名数组第一个元素
  • 将c的值赋给d
  • 将d的值赋给匿名数组第二个元素
  • 将匿名数组第一个元素的值赋给c 总结一下:关键点就是生成了一个[20, 10]的匿名数组
    注意点:d=c 这一步先将 变量d的值重写,再将表达式的结果赋给匿名数组第二个元素

三、数组扩展认识

在数组中,有以下几种特殊的情况需要我们了解一下。

3.1 空位数组

空位数组就是数组中包含空元素,类比一下占位置,占了位置,位置上没人。对于空位元素我们需要注意几点:

  • 空元素可以读写,length 属性不排斥空位
  • 空位元素读取返回值是 undefined,但是和元素的值为 undefined 是不同概念,注意区分
  • forEach() 方法、for/in 语句以及 Object.keys() 方法 在循环空元素的时候回直接跳过,这也是为什么我们需要了解空位数组
let arr = new Array(3)
console.log(arr) // [empty × 3]
console.log(arr.length) // 3

let arr1 = [1, 2, ,]
console.log(arr1) // [1, 2, empty]
console.log(arr1.length) // 3

let arr2 = [4, 5, 6]
delete arr2[1]
console.log(arr2) // [4, empty, 6]
console.log(arr2[1]) // undefined
console.log(arr2.length) // 3

// 以上三种方式均能生成空位数组

for...in 循环空位数组示例

3.2 关联数组

“关联数组”是一种具有特殊索引方式的数组。不仅可以通过整数来索引它,还可以使用字符串或者其他类型的值(除了NULL)来索引它。

关联数组就是与数组关联的对象,我们需要注意以下几点:

  • 关联数组的索引值不是非负的整数而是任意的标量
  • 关联数组的元素没有特定的顺序
  • 关联数组是一种数据格式,被称为哈希表。哈希表的数据检索速度要优于数组。
let arr = [];
arr['世界那么大'] = '我想去看看'
arr['你咋不上天呢'] = '太重了'
arr['他人笑我太疯狂'] = '我笑他人看不穿'
console.log(arr) 
// [世界那么大: "我想去看看", 你咋不上天呢: "太重了", 他人笑我太疯狂: "我笑他人看不穿"]

console.log(arr.length) // 0

console.log(arr['他人笑我太疯狂']) // 我笑他人看不穿

arr[0] = 'aaaa'
console.log(arr)
// ["aaaa", 世界那么大: "我想去看看", 你咋不上天呢: "太重了", 他人笑我太疯狂: "我笑他人看不穿"]
console.log(arr.length) // 1

// 从两次的length 不难看出,关联数组是没有实际长度的,只有按照非负整数为索引,才能使得数组具备长度。

3.3 伪类数组

伪类数组,也被叫做类数组,即类似数组结构的对象。拥有 length 属性,其它属性(索引)为非负整数(对象中的索引会被当做字符串来处理,伪数组同样有 length 属性。伪类数组的数据类型是object,数组的数据类型的是 Array。

常见的伪类数组

  • 函数内部的 arguments
  • DOM 对象列表(比如通过 document.getElementsByTagName 得到的列表)
  • jQuery 对象(比如 $("div") )

3.3.1 伪类数组转换成真实数组

想要转化伪类数组为真实数组,那我们需要先知道伪类数组可以使用真实数组的哪些东西,length、index

3.3.1.1 for循环转换

let obj = { // 仿制不完全伪类数组
  0: 'name',
  1: 'name',
  2: 'name',
  length: 3
}
let newArr = [] // 真实数组
for(let i = 0; i < obj.length; i++) {
  newArr.push(obj[i])
}
console.log(newArr) // ["name", "name", "name"]

3.3.1.2 使用解构赋值

function test() {
  console.log(arguments)
  let newArr = [...arguments]
  console.log(newArr)
}
test('name','test','sex')

3.3.1.3 使用slice方法

function test() {
  console.log(arguments)
  let newArr = Array.prototype.slice.call(arguments)
  console.log(newArr)
}
test('你好','坚持','牛皮')

3.3.1.4 使用es6的Array.from()方法

function test() {
  console.log(arguments)
  let newArr = Array.from(arguments)
  console.log(newArr)
}
test('百年孤独','朝花夕拾','高粱')

四、判断数据是否为数组类型

4.1 Array.isArray()来判断

let a = 'bbb'
let b = []
let c = {}
console.log(Array.isArray(a)) // false
console.log(Array.isArray(b)) // true
console.log(Array.isArray(c)) // false

4.2 Object.prototype.toString()来判断(需要借助call/apply/bind)

call/apply/bind 这里主要是用来改变this指向的,具体怎么实现的我们放到this指向整理的时候在探讨。
let a = 'bbb'
let b = []
let c = {}
console.log(Object.prototype.toString.call(a)) // [object String]
console.log(Object.prototype.toString.apply(b)) // [object Array]
console.log(Object.prototype.toString.bind(c)()) // [object Object]

4.3 使用 instanceof 来判断

let a = 'bbb'
let b = []
let c = {}
console.log(a instanceof Array) // false
console.log(b instanceof Array) // true
console.log(c instanceof Array) // false

// 需要注意的一点是,数组类型本身也是对象类型的一种所用,我们写判断条件的时候需要注意
console.log(b instanceof Object) // true

4.4 使用 constructor 来判断

let a = 'bbb'
let b = []
let c = {}
console.log(a.constructor === Array) // false
console.log(b.constructor === Array) // true
console.log(c.constructor === Array) // false

五、数组相关方法自定义枚举

5.1 push

5.1.1 功能概述

自定义方法名: myPush
功能: 在原数组末尾添加新元素
参数: 非必须
返回值: 数组长度
是否改变原数组:

5.1.2 实现思路

  • 没有参数,返回原数组的长度
  • 原数组的长度和元素下标最大值差1,也就是说数组的下一个元素的下标就是此时数组的长度
  • 参数是存储在伪数组arguments中的,可以使用其length,index
  • 循环增加后返回 数组的长度

5.1.3 自定义实现

Array.prototype.myPush = function() {
  if(arguments.length > 0) {
  	for(let i = 0; i < arguments.length; i++) {
  	  this[this.length] = arguments[i]
    }
  }
  return this.length
}

5.1.4 测试

let arr = ['aa']
console.log(arr.myPush()) // 1
console.log(arr) // ["aa"]
console.log(arr.push()) // 1
console.log(arr) // ["aa"]

console.log(arr.myPush(1,2)) // 3
console.log(arr) // ["aa", 1, 2]
console.log(arr.push(3, 4)) // 5
console.log(arr) // ["aa", 1, 2, 3, 4]

5.2 unshift

5.2.1 功能概述

自定义方法名: myUnshift
功能: 在原数组开始添加元素
参数: 非必须
返回值: 数组长度
是否改变原数组:

5.2.2 实现思路

  • 没有参数,返回原数组的长度
  • 参数是存储在伪数组arguments中的,根据参数的顺序传入顺序,插入到原数组的顺序保持一致
  • 原数组的第一位在插入后的下标就是arguments的长度,以此后推
  • 设置中间量数组,做一个两个数组的合并体
  • 将中间量数组的元素依次赋值给 this
  • 循环结束后返回 数组的长度

5.2.3 自定义实现

Array.prototype.myUnshift = function() {
  if(arguments.length > 0) {
  	let tempArr = []
  	for(let i = 0; i < arguments.length; i++) {
  	  tempArr[i] = arguments[i]
    }
    for(let j = 0; j < this.length; j++){
      tempArr[tempArr.length] = this[j]
    }
    for(let k = 0; k < tempArr.length; k++){
      this[k] = tempArr[k]
    }
  }
  return this.length
}

5.2.4 测试

let arr = ['aa']

console.log(arr.myUnshift())
console.log(arr.unshift())

console.log(arr.myUnshift('bb', 'cc'))
console.log(arr)

console.log(arr.unshift('dd', 'ff'))
console.log(arr)

5.3 pop

5.3.1 功能概述

自定义方法名: myPop
功能: 删除数组最后一位元素
参数:
返回值: 删除的元素
是否改变原数组:

5.3.2 实现思路

  • 需要一个返回的变量来承接删除的元素,用来返回
  • 如果数组中有元素,再进行赋值,没有直接返回 undefined
  • 方法调用一次原数组的长度减 1

5.3.3 自定义实现

Array.prototype.myPop = function() {
  var delData
  if(this.length > 0) {
    delData  = this[this.length - 1]
    this.length -= 1
  }
  return delData
}

5.3.4 测试

let arr = ['aa', 'bb']
console.log(arr.myPop())
console.log(arr)
console.log(arr.myPop())
console.log(arr)
console.log(arr.myPop())
console.log(arr)

5.4 shift

5.4.1 功能概述

自定义方法名: myShift
功能: 删除数组第一位元素
参数:
返回值: 删除的元素
是否改变原数组:

5.4.2 实现思路

  • 需要一个返回的变量来承接删除的元素,用来返回
  • 如果数组中有元素,再进行赋值,没有直接返回 undefined
  • 如果数组长度大于1 再进行循环赋值,整体前移一位,否则直接赋值,并缩减长度

5.4.3 自定义实现

Array.prototype.myShift = function() {
  var delData
  if(this.length > 0) {
    delData  = this[0]
    if(this.length > 1) {
      for(var i = 0; i < this.length - 1; i++) {
        this[i] = this[i + 1]
      }
    }
    this.length -= 1
  }
  return delData
}

5.4.4 测试

let arr = ['aa', 'bb', 'cc']

console.log(arr.myShift())
console.log(arr)
console.log(arr.myShift())
console.log(arr)
console.log(arr.myShift())
console.log(arr)

5.5 indexOf

5.5.1 功能概述

自定义方法名: myIndexOf(searchvalue, start)
功能: 返回某个指定的字符串值在字符串中首次出现的位置
参数: searchvalue->必需:规定需检索的字符串值
参数: start->非必需:规定检索的起始位置,不传则从首位开始
返回值: 字符串中首次出现的下标,没有匹配则返回 -1
是否改变原数组:

5.5.2 实现思路

  • 不传参数 返回 -1
  • 不传起始位置,从0 开始检索 匹配返回相应下标并阻断流程,否则返回-1
  • 传入非数字起始位置,同上从0开始检索
  • 传入起始位置浮点型 向下取整
  • 传入起始位置 小于0 或大于数组长度 返回-1
  • 注意: parseInt(-0.2) = -0

5.5.3 自定义实现

Array.prototype.myIndexOf = function (searchVal, start) {
  if(!searchVal) return -1

  let nowStart
  if (start && (typeof start === 'number')) {
    nowStart = parseInt(start)
    if(nowStart === -0) nowStart = 0 // 容易忽略的点
    if(nowStart > this.length || nowStart < 0)  return -1
  } else nowStart = 0

  for(; nowStart < this.length; nowStart++) {
    if(this[nowStart] === searchVal) return nowStart
  }
  return -1
}

5.5.4 测试

let arr = [1, 18, 30, 23, 15, 22]
console.log('------不穿参数对比(官方->自定义)--------')
console.log(arr.indexOf())
console.log(arr.myIndexOf())
console.log('------不穿起始位置对比(官方->自定义)--------')
console.log(arr.indexOf(23))
console.log(arr.myIndexOf(23))
console.log('------传入整型起始位置对比(官方->自定义)--------')
console.log(arr.indexOf(23, 4))
console.log(arr.myIndexOf(23, 4))
console.log('------起始位置小于0对比(官方->自定义)--------')
console.log(arr.indexOf(23, -1))
console.log(arr.myIndexOf(23, -1))
console.log('------起始位置大于数组长度对比(官方->自定义)--------')
console.log(arr.indexOf(23, 6))
console.log(arr.myIndexOf(23, 6))
console.log('------起始位置非数字对比(官方->自定义)--------')
console.log(arr.indexOf(23, 'a'))
console.log(arr.myIndexOf(23, 'a'))
console.log('------起始位置浮点型对比(官方->自定义)--------')
console.log(arr.indexOf(23, 3.6))
console.log(arr.myIndexOf(23, 3.6))

5.6 reverse

5.6.1 功能概述

自定义方法名: myReverse()
功能: 数组元素对调
参数:
返回值: 颠倒顺序后的数组
是否改变原数组:

5.6.2 实现思路

  • 改变原数组 返回 改变后的原数组
  • 使用中间量承接

5.6.3 自定义实现

Array.prototype.myReverse = function () {
  let tempArr = [];
  for(let i = this.length -1; i >= 0; i--) tempArr[tempArr.length] = this[i]
  for(let k = 0; k < tempArr.length; k++) this[k] = tempArr[k]
  return this
}

5.6.4 测试

let arr = [1, 18, 30, 23, 15, 22]
console.log('------ 反转结果对比(官方->自定义)--------')
console.log(arr.reverse())
console.log(arr)
console.log(arr.myReverse())
console.log(arr)

5.7 concat

5.7.1 功能概述

自定义方法名: myConcat()
功能: 数组合并任意值
参数: 任意
返回值: 合并后的数组
是否改变原数组:

5.7.2 实现思路

  • 声明一个新数组 深拷贝调用者
  • 如果没有参数直接返回新数组
  • 如果参数为数组类型,将该参数中的元素拉出合并
  • 如果参数为其他类型,直接合并

5.7.3 自定义实现

Array.prototype.myConcat = function () {
  let currentArr = []
  if(this.length > 0) {
    for(let j = 0; j < this.length; j++) currentArr[j] = this[j]
  }
  if(arguments.length > 0) {
    for(let i = 0; i < arguments.length; i++) {
      if (Array.isArray(arguments[i])) {
        for(let k = 0; k < arguments[i].length; k++) currentArr[currentArr.length] = arguments[i][k]
      }else currentArr[currentArr.length] = arguments[i]
    }
  }
  return currentArr
}

5.7.4 测试

let arr = [1, 18, 30, 23, 15, 22]
console.log('------ 任意参数合并结果对比(官方->自定义)--------')
console.log(arr.concat(0, [11, 24, [24, 33]], {sex:22}, '', null, undefined))
console.log(arr.myConcat(0, [11, 24, [24, 33]], {sex:22}, '', null, undefined))
console.log('------ 不传参数合并结果对比(官方->自定义)--------')
console.log(arr.concat())
console.log(arr.myConcat())
console.log('------ 原数组--------')
console.log(arr)

六、小结

数组官方给出的方法我看了一下大概有30个,我这边整理了7个基础的,实现的不是很好,大家有什么建议剩下的我会补充一个方法专题出来,这篇文章也有有点长了,希望大家能够读到最后。我这边尽快把剩下的方法整理出来,大家一起进步!我是阿树,下期干货十足,我们下期见!

七、声明

  • 创作不易,转载请注明来源
  • 创作不易,copy请远离