JavaScript:JavaScript数组探究

JavaScript数组探究

数组作为最基础的数据结构,我们不仅要熟练掌握它,还要了解它的许多特性

js的数组很灵活,和其他语言的数组不一样,js的数组可以存储不同类型的值

创建一维数组的两种方法

1.数组字面量法

需要注意的是:使用数组字面量法创建数组并不会调用Array构造函数

let arr1 = [1, 2, 3, 4, 5, 11, 13, 24];

2.使用Array的构造函数,new一个数组

let arr2 = new Array();
// 指定数组长度
let arr3 = new Array(8);
// 指定长度,且填充1
let arr4 = new Array(4).fill(24);

// new操作符可以省略,一个长度为二的数组
let arr6 = Array(2, 3);
console.log("arr6:", arr6);

// 当参数只有一个数字的时候,就是创建一个长度为传入参数的数组
let arr7 = Array(3);
console.log("arr7:", arr7);


// 遍历数组
arr4.forEach((item, index) => {
    console.log(item, index);
})

// 还可以用map来遍历数组
// map 方法在调用形式上与 forEach 无异,
// 区别在于 map 方法会根据你传入的函数逻辑对数组中每个元素进行处理、进而返回一个全新的数组。
console.log("map遍历");
let arr5 = arr4.map((item, index) => {
    console.log(item, index);
    return item + 11;
})·

二维数组的创建

一维数组可以使用数组字面量和使用Array的构造函数创建

那么二维数组呢?

下面来看一下这样初始化对不对

let arr1 = new Array(3).fill([]);
// 现在我们来改变一下二维数组里面的值
arr1[0][0] = 35;
console.log(arr1); // 这里发现里面的值全都变成了35,这是为什么呢

这里是因为 fill 的工作机制导致的。 当给 fill 传递一个入参时,如果这个入参的类型是引用类型,那么 fill 在填充坑位时填充的其实就是入参的引用。所以fill里面的数组是同一个引用、指向的是同一块内存空间,它们本质上是同一个数组所以应该用for循环来初始化一个二维数组

for (let i = 0; i < 4; i++) {
  arr2[i] = [];
}

数组的一些方法

pop/push, shift/unshift 方法

  • push 在末端添加一个元素

  • pop 从末端取出一个元素

  • unshift 在队列首端添加一个元素,整个队列往后移一位

  • shift 取出队列首端的一个元素,整个队列往前移一位

这里会有一个性能问题,那就是pushpop的性能会比shiftunshift高,因为shiftunshift会移动整个数组,这样很浪费时间。

遍历方式

数组的遍历方式有三种

let arr = ["beiyep", "kobe", "james"];

// 用数组的长度遍历
for(let i = 0; i < arr.length; i++) {
    console.log(arr[i]);
}

// 用for of遍历
for(let item of arr) {
    console.log(item);
}

// 用for in遍历
for(let item in arr) {
    console.log(item);
}

console.log("pause");

需要说明的是,for in返回的是数组的下标值,并且要注意:

  1. for..in 循环会遍历 所有属性,不仅仅是这些数字属性。

    在浏览器和其它环境中有一种称为“类数组”的对象,它们 看似是数组。也就是说,它们有 length 和索引属性,但是也可能有其它的非数字的属性和方法,这通常是我们不需要的。for..in 循环会把它们都列出来。所以如果我们需要处理类数组对象,这些“额外”的属性就会存在问题。

    来看下面的🌰:

    Array.prototype.myfun = function() {
        alert('myfun');
    }    
    var arr2 = [0,1,2,3];
    
    for (var i in arr2) {
        console.log(arr2[i]);
    }
    
    console.log(Array.prototype)

    运行结果如下:

    上面的例子很好的反映了for…in…循环的缺点,原本只想循环取出该数组的数据,但是由于之前给数组添加了原型函数,导致循环的结果多了一个函数

  2. for..in 循环适用于普通对象,并且做了对应的优化。但是不适用于数组,因此速度要慢 10-100 倍。当然即使是这样也依然非常快。只有在遇到瓶颈时可能会有问题。但是我们仍然应该了解这其中的不同。

数组中的搜索

  1. 严格相等

js提供了indexOf/lastIndexOf 和 includes三种严格相等的搜索方法。其中

  • arr.indexOf(item, from) 从索引 from 开始搜索 item,如果找到则返回索引,否则返回 -1
  • arr.lastIndexOf(item, from) —— 和上面相同,只是从右向左搜索。
  • arr.includes(item, from) —— 从索引 from 开始搜索 item,如果找到则返回 true,如果没找到,则返回 false

请注意,这些方法使用的是严格相等 === 比较。所以如果我们搜索 false,会精确到的确是 false 而不是数字 0

let arr = [1, 11, 24, 35, 13, false, true];

// 从索引2开始搜索1
console.log(arr.indexOf(1, 2)); // -1

// 从索引3开始搜索13
console.log(arr.indexOf(13, 3)); // 3

// 从零开始搜索true
console.log(arr.indexOf(true)); // 6

console.log(arr.indexOf(0)); // -1

如果我们想检查是否包含某个元素,并且不想知道确切的索引,那么 arr.includes 是首选。

此外,includes 的一个非常小的差别是它能正确处理NaN,而不像 indexOf/lastIndexOf

let arr1 = [NaN]
console.log(arr1.indexOf(NaN)); // -1
console.log(arr1.includes(NaN)); // true
  1. 断言函数搜索

arr.find和arr.findIndex是js提供的断言函数搜索,每个索引都会调用这个函数。

arr.find的语法如下:

let result = arr.find(function(item, index, array) {
  // 如果返回 true,则返回 item 并停止迭代
  // 对于假值(false)的情况,则返回 undefined
});

arr.findIndex 方法(与 arr.find 方法)基本上是一样的,但它返回找到元素的索引,而不是元素本身。并且在未找到任何内容时返回 -1

Array.isArray

由于数组是基于对象的,不构成单独的语言类型,所以 typeof 不能帮助从数组中区分出普通对象:

console.log(typeof {}); // object
console.log(typeof []); // object

为了解决这个问题,Array.isArray应运而生,如果 value 是一个数组,则返回 true;否则返回 false

console.log(Array.isArray({})); // false
console.log(Array.isArray([])); // true

数组中方法的补充说明

几乎所有调用函数的数组方法 —— 比如 findfiltermap,除了 sort 是一个特例,都接受一个可选的附加参数 thisArg

比如:

arr.find(func, thisArg);
arr.filter(func, thisArg);
arr.map(func, thisArg);
// ...
// thisArg 是可选的最后一个参数

举个🌰:

let army = {
    minAge: 18,
    maxAge: 27,
    canJoin(user) {
      return user.age >= this.minAge && user.age < this.maxAge;
    }
  };
  
  let users = [
    {age: 16},
    {age: 20},
    {age: 23},
    {age: 30}
  ];
  
  // 找到 army.canJoin 返回 true 的 user
	let soldiers = users.filter(army.canJoin, army);
  // let soldiers = users.filter(army.canJoin); // 错误
  
  console.log(soldiers.length); // 2
  console.log(soldiers[0].age); // 20
  console.log(soldiers[1].age); // 23

如果在上面的示例中我们使用了 users.filter(army.canJoin),那么 army.canJoin 将被作为独立函数调用,并且这时 this=undefined,从而会导致即时错误。所以我们这里一般都用剪头函数。

数组的本质

数组是一种特殊的对象,比如数组访问元素的方式是arr[0]和对象obj[key]很相似,其中arr是对象,数字用作键。数组和类相似,也是引用类型。但是数组又拓展了对象,因为数组可以处理有序的数据集合。

------------- 本文结束 感谢阅读 -------------