【JavaScript入门笔记05 数据类型Ⅲ 数组详解】

笔记参考javascript.info中文站

数组

1. 声明和取数

常规声明有两种方法:

  • let arr = new Array();
    麻烦而且有特殊的问题,一般不使用

  • let arr = [];
    很常用,在括号内就可以初始化:

let fruits = ["Apple", "Orange", "Plum"];

取数方面,数组从 0 开始编号,通过方括号取对应位置的数字

let fruits = ["Apple", "Orange", "Plum"];

alert( fruits[0] ); // Apple

也可以通过这种方式替换元素,甚至添加新元素

let testArr = [5, 3, 'd'];
testArr[6] = 1;
alert(testArr); // [ 5, 3, 'd', <3 empty items>, 1 ]

另外,从上面的例子我们可以看出,类似 python,Javascript 的数组可以存储任何类型的元素

在调取倒数第几位元素时,我们需要注意,不能直接上负数,需要用 at() 函数:

let fruits = ["Apple", "Orange", "Plum"];

alert( fruits[fruits.length-1] ); // Plum,这样写最麻烦
alert( fruits[-1] ); // undefined,这样最直观,符合我们的习惯,但是是错误的
alert( fruits.at(-1) ); // Plum,我们应该这样写

2. pop/push, shift/unshift 方法
  • pop()
    取出并返回数组的最后一个元素

  • push( value )
    在数组末端添加若干个元素

  • shift()
    取出并返回数组的第一个元素

  • unshift()
    在数组首端添加若干个元素

举个例子:

let fruits = ["Apple", "Orange", "Pear"];

alert( fruits.pop() ); // 移除 "Pear" 然后 alert 显示出来
alert( fruits ); // Apple, Orange

fruits.push("Pear");
alert( fruits ); // Apple, Orange, Pear

alert( fruits.shift() ); // 移除 Apple 然后 alert 显示出来
alert( fruits ); // Orange, Pear

fruits.unshift('Apple');
alert( fruits ); // Apple, Orange, Pear

push/pop 方法运行的比较快,而 shift/unshift 比较慢。

也就是末端进行操作更方便,这是因为首端操作需要将后面一系列元素全都向前或向后挪动,而末尾操作只需要移动最后的若干元素

3. 内部本质

明确一点:数组是一种特殊的对象
其实与 obj[key] 相同,其中 arr 是对象,而数字用作键(key),其中的元素就是值,而区别在于有无顺序

所以实际上,数组的内部实现是基于对象的,因此很多对象可以进行的操作,从语法层面讲数组也可以进行,举个例子:

let fruits = []; // 创建一个数组

fruits[99999] = 5; // 分配索引远大于数组长度的属性,中间的就是空洞
fruits.age = 25; // 创建一个具有任意名称的属性

但我们创建数组的目的就是构造一个有序集合,因此请不要利用语法漏洞随意书写

4. for 循环和 length

最古朴的方式:

let arr = ["Apple", "Orange", "Pear"];

for (let i = 0; i < arr.length; i++) {
  alert( arr[i] );
}

更现代的方式,for..of

let fruits = ["Apple", "Orange", "Plum"];

// 遍历数组元素
for (let fruit of fruits) {
  alert( fruit );
}

两种方式各有缺点,for 循环太麻烦、可读性差,for...of 无法获取元素索引,只能获取值。

事实上,我们知道数组是一种特殊的对象,因此也可以用 for..in 来遍历
但问题是,有些对象长得特别像数组,当我们统一使用 for..in 来循环的时候,就无法区分他们的区别,从而导致一些错误:

  • “类数组” 中有一些隐形的属性,尽管平时看不到,循环过程中依然会遍历到,这些内容不是我们想要的
  • for…in 循环适用于普通对象,并且做了对应的优化。但是不适用于数组,因此速度要慢 10-100 倍

5. 一些特性
  • length
    当我们修改数组的时候,length 属性会自动更新
    有趣的是,我们可以手动修改 length,此时数组会自动从头截取对应长度的子数组
let arr = [1, 2, 3, 4, 5];

arr.length = 2; // 截断到只剩 2 个元素
alert( arr ); // [1, 2]
  • 多维数组
    数组里的元素也可以是另一个数组,这就组成了多维数组,就好像矩阵
let matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

alert( matrix[1][1] ); // 最中间的那个数
  • toString
    数组转字符串有独特的一套逻辑
    显示转换:
let arr = [1, 2, 3];

alert( arr ); // 1,2,3
alert( String(arr) === '1,2,3' ); // true

   隐式转换:

alert( [] + 1 ); // "1"
alert( [1] + 1 ); // "11"
  • 不要使用 == 比较数组
    数组的本质是对象,只有两个同源的对象才会被判定为相等,因此即使两个数组元素一模一样,用 == 判断也是 false
     
    另外,在数组与原始类型的比较中,数组会自动转换成字符串形式,这更证明用 == 比较数组是没意义的
const testArr = [5, 3];
console.log( '5,3' == testArr ); // true

6. 添加/移除数组元素

移除元素可以使用对象使用的 delete ,但移除后对应位置的元素只是会变成 undefined,而我们希望的是将这个位置一并删除

let arr = ["I", "go", "home"];

delete arr[1]; // remove "go"
alert( arr[1] ); // undefined

所以我们使用的方法是 arr.splice(start[, deleteCount, elem1, ..., elemN])
这个方法可以进行增删改三种操作

let arr = ["I", "study", "JavaScript", 1, 2, 3];

// 不写后面的元素,只有起始位置和个数,则从起始位置开始,删除对应个数个元素
arr.splice(1, 1); // 从索引 1 开始删除 1 个元素
alert( arr ); // ["I", "JavaScript", 1, 2, 3]

// 删除数组的前三项,并使用对应内容代替它们
arr.splice(0, 3, "Let's", "dance");
alert( arr ) // 现在 ["Let's", "dance", 2, 3]

// 将deleteCount设置成0,那就不需要删除任何元素,直接新增
arr.splice(2, 0, "complex", "language");
alert( arr ); // ["Let's", "dance", "complex", "language", 2, 3]

注意,splice 方法支持负向索引


除此之外,还有 arr.slice([start], [end]) 可以帮我们返回子数组

let arr = ["t", "e", "s", "t"];

alert( arr.slice(1, 3) ); // e,s(复制从位置 1 到位置 3 的元素)
alert( arr.slice(-2) ); // s,t(复制从位置 -2 到尾端的元素)

**`arr.concat(arg1, arg2...)`** 方法可以创建新数组,数组的元素由 arg 参数决定:
  • 如果参数是原始类型,那么直接写进新数组
  • 如果参数也是数组,那就把该数组遍历一遍,内部的元素再写进新数组,而如果内部元素本身还是数组,那就直接当作数组写入
  • 如果参数是对象,那就被当做对象写作 [object Object]
  • 如果对象内好巧不巧有一个属性 Symbol.isConcatSpreadable
    ,那按照顺序,该属性之前的属性的值会当作一般元素写入新数组,而该属性本身及后面的属性就会被抛弃

7. 遍历:forEach

arr.forEach(function(item, index, array) 方法允许为数组的每个元素都运行一个函数。

// 对每个元素调用 alert
["Bilbo", "Gandalf", "Nazgul"].forEach(alert);

// 而这段代码更详细地介绍了它们在目标数组中的位置:
["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => {
  alert(`${item} is at index ${index} in ${array}`);
});

注意,其中的 ${index}${array} 都是特定字符,有固定的意义

8. 在数组中搜索
8.1 indexOf/lastIndexOf 和 includes

indexOf(item, from)/lastIndexOf(item, from)includes(item, from) 类似,都是看看某元素是否在数组内,区别在于:前者返回 index 或 -1,后者返回 true 或 false

参数 item 是索引目标内容,from 则是一个位置,、从该位置开始查询

let arr = [1, 0, false];

alert( arr.indexOf(false) ); // 2
alert( arr.lastIndexOf(0) ); // 1
alert( arr.includes(1) ); // true

从上面例子中我们还可以注意到一个小细节,indexOf(item, from) 在查找 “false” 的过程中返回的并不是 “0” 的位置 1,而是 false 的位置 2,这是因为该方法使用的比较是严格比较 === 所以 false 和 0 不相等。

还有一点需要注意,indexOf 不能处理 NaN,而 include 可以

8.2 find 和 findIndex/findLastIndex

如果想查询一个由对象组成的数组,则需要 arr.find(function(item, index, array) 方法
其中 item 是元素(对象),index 是对应的索引,array 是目标数组
如果找到了对应内容,则返回 true 并返回对应的 item,否则返回 undefined

find() 的用法比较特殊,需要用箭头 =>

let users = [
  {id: 1, name: "John"},
  {id: 2, name: "Pete"},
  {id: 3, name: "Mary"}
];

let user = users.find(item => item.id == 1);

alert(user.name); // John

8.3 filter

find() 方法只能返回第一个找到的对象,如果需要返回很多,则需要使用 arr.filter(function(item, index, array) 方法,找到则返回包含所有符合条件的对象组成的数组,如果没有符合的则返回空数组

let users = [
  {id: 1, name: "John"},
  {id: 2, name: "Pete"},
  {id: 3, name: "Mary"}
];

// 返回前两个用户的数组
let someUsers = users.filter(item => item.id < 3);

alert(someUsers.length); // 2

9. 转换数组
9.1 map

map 函数对数组的每个元素都调用内部的一个简单的操作,并返回结果数组。

举个例子:

// 将数组的元素变成每个元素的字符串长度
let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length);
alert(lengths); // 5,7,6

9.2 sort(fn)

arr.sort 方法对数组进行 原位(in-place) 排序,也就是在原有数组内进行修改,而非生成一个新数组,但事实上,该函数依然会返回新数组

另外注意,比较的时候会采用参数 fn 的比较方法进行排序,如果没有这个函数,则默认采用字符串比较,而比较函数只需要返回一个正数表示“大于”,一个负数表示“小于”

举个例子:

let arr = [ 15, 2, 13 ];

// 该方法重新排列 arr 的内容
arr.sort();
alert( arr );  // 2, 13, 15

// 采用这个比较方法
function compareNumeric(a, b) {
  if (a > b) return 1;
  if (a == b) return 0;
  if (a < b) return -1;
}

arr.sort(compareNumeric);
alert(arr);  // 2, 13, 15

比较函数也可以采用以前提过的常用库中的函数,如 localeCompare() 函数

9.3 reverse

arr.reverse 方法用于颠倒 arr 中元素的顺序

举个例子:

let arr = [1, 2, 3, 4, 5];
arr.reverse();

alert( arr ); // 5,4,3,2,1

9.4 split 和 join

split() 函数可以将字符串转成数组,由某一符号进行分割标准:

let names = 'Bilbo, Gandalf, Nazgul';

let arr = names.split(', '); // 由 “, ” 分割

for (let name of arr) {
  alert( `A message to ${name}.` ); // A message to Bilbo(和其他名字)
}

arr.join(glue) 函数恰好相反,可以让数组的元素粘合成由 glue 分割的字符串:

let arr = ['Bilbo', 'Gandalf', 'Nazgul'];

let str = arr.join(';'); // 使用分号 ; 将数组粘合成字符串

alert( str ); // Bilbo;Gandalf;Nazgul

9.5 reduce/reduceRight

reduce() 方法和前面的 map() 非常相似,都是对数组内的元素一个个进行函数调用,而区别在于,reduce/reduceRight 是对上一个函数的结果进行函数操作,也就是 “累加” 式的:

let value = arr.reduce(function(accumulator, item, index, array) {
  // ...
}, [initial]);

举个例子:

let arr = [1, 2, 3, 4, 5];

let result = arr.reduce((sum, current) => sum + current, 0);
alert(result); // 15

可以看到,上一个调用的结果如何成为下一个调用的第一个参数
此外,初始值,也就是上例中的 “0” 是可以省略的,默认初始值是数组的第一个元素,但如果数组为空则会报错

reduceRightreduce 一样,只是顺序是从右往左

10. 其他特性
10.1 Array.isArray()

数组是基于对象的,不构成单独的语言类型,因此使用 typeof() 查询数组的类型会得到 Object,无法区分

alert(typeof {}); // object
alert(typeof []); // object(相同)

想要判断数组,应该使用 Array.isArray(value) 方法

alert(Array.isArray({})); // false

alert(Array.isArray([])); // true

value 是数组则返回 true 否则返回 false

10.2 “thisArg”

几乎所有调用函数的数组方法 —— 比如 findfiltermap,除了 sort,都接受一个可选的附加参数 thisArg,但它很少被使用。

thisArg 参数的值在 func 中变为 this,者可以帮我们自定义 this 指的是哪个变量

举个例子:

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); // 如果没有第二个参数 army,系统就找不到this是哪个,就会返回 undefined,导致报错

alert(soldiers.length); // 2
alert(soldiers[0].age); // 20
alert(soldiers[1].age); // 23
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值