JavaScript权威指南(第7版) 笔记 - 第 7 章 数组

能用代码说清楚的,绝不多废话!!!!!!

Linux创始人Linus的名言:Talk is cheap,show me the code ! ,博主技术博文会精心给出能说明问题的范例代码!!!!!

⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️

第7章 数组

  • 数组是值的有序集合,同一数组的数组元素可以是不同的类型;

例:

let arr = [1,'2',{a:3},function(){}];
  • JavaScript数组是动态的,它们会按需增大或缩小;
let arr = [];// 空数组
arr[0] = 0; // 增大
arr[1] = 1; // 增大
arr.length = 1; // 缩小
delete arr[0]; // 缩小
  • JavaScript数组可以是稀疏的,即元素不一定具有连续的索引,中间可能有空位;
    例:
let data = [7, 8, 9];
data[10] = '';// 动态的、稀疏的

索引3~9都是空位。我们在浏览器中debug的截图如下
在这里插入图片描述

JavaScript数组是一种特殊的JavaScript对象,因此数组的索引更像是属性名,只不过这个属性名是整数而已;(看了上图我们更能深刻体会这句话)

console.log(9 in data);      // => false
  • JavaScript字符串的行为类似字母数组
console.log("abc"[2]); // => c

可以通过 [] 读取字符串中的字符

  • ES6增加了一批新的数组类,统称为“定型数组”,定型数组具有固定长度和固定的数值元素类型。定型数组具有极高的性能

TODO

参考博主其他博文:定型数组

7.1 创建数组

创建数组有4中方式:
1、数组字面量;
2、对可迭代对象使用…扩展操作符;
3、Array() 构造函数;
4、工厂方法 Array.of()和 Array.from();

7.1.1 数组字面量

(1)元素可以是常量

let empty = [];                 // 空数组
let primes = [2, 3, 5, 7, 11];  // 5个数值元素的数组
let misc = [1.1, true, "a",];   // 不同类型元素的数组,末尾逗号会被忽略misc的length为3

⚠️ 数组中末尾的逗号会被忽略。

(2)元素可以是变量或表达式

let base = 1024;
let table = [base, base + 1, base + 2, base + 3];

(3)逗号之间没有值的情况

let count = [1, , 3]; // 索引0、2处有元素 length为3,
let undefs = [, ,];  // 忽略末尾逗号,length为2

在这里插入图片描述
在这里插入图片描述

⭐️⭐️ 把数组看作对象、索引看作对象的属性(属性不需要数字连续),上面数组的表现就很容易理解了。

7.1.2 扩展操作符

let a = [1, 2, 3];
let b = [0, ...a, 4];  // b == [0, 1, 2, 3, 4]

...a 想象成代表数组a的所有元素

扩展操作符

(1)扩展操作符创建数组(浅)副本

let original = [1, 2, 3];
let copy = [...original];
copy[0] = 0;  // 修改copy中的元素值,不影响original中元素值
original[0]   // => 1

(2)扩展操作符适用于任何可迭代对象

可迭代对象:数组、字符串、Set、Map

let arr =[];
// 1 字符串
arr = [..."abc"];
console.log(arr);// ['a', 'b', 'c']
// 2、数组
let other_arr = [1,2,3];
arr = [...other_arr];
console.log(arr);// [1, 2, 3]

// 3、Set
let set = new Set().add(1).add(2).add(3);
arr = [...set];
console.log(arr);// [1, 2, 3]

// 4、Map
let map = new Map().set("one",1).set("tow",2).set("three",3);
arr = [...map];
console.log(arr);// [['one', 1],['tow', 2],['three', 3]]

(3)应用Set数组去重

let letters = [..."hello world"];
letters = [...new Set(letters)]  // => ["h","e","l","o"," ","w","r","d"]

7.1.3 Array()构造函数

let a1 = new Array();               // 等价于 let a1 = [];
let a2 = new Array(10);             // 等价于 let a2 = [];a2.length = 10;
let a3 = new Array(3, 2, 1, "t,t"); // 等价于 let a3 = [3, 2, 1, "t,t"];

各个数组内部状态如下图:
在这里插入图片描述
字面量创建数组更简单,且完全替代构造函数创建数组。

⚠️ ⚠️ new Array(n) 这种方式创建数组,从上图中看,并不会预先分配空间。但对JavaScript引擎可能是有用的,可以根据n的大小来决定分配在内存的哪块区域 (根据程序访问的局部性原理,连续的内存可能会带来性能优势)。

在JavaScript中,数组的内存并不需要连续的内存区域。事实上,JavaScript的数组在内存中的实现与许多其他编程语言中的数组有本质的区别。

在JavaScript中,数组实际上是一种特殊的对象,用于存储有序的元素集合。这些元素可以是任何数据类型,并且数组的大小可以动态地增长和缩小。JavaScript的数组底层实现通常依赖于一种称为“散列表”或“哈希表”的数据结构,而不是传统的连续内存块。

使用散列表或哈希表作为底层数据结构意味着数组的每个元素都可以通过其索引(键)直接访问,而不需要遍历整个数组。这种实现方式提供了高效的插入、删除和查找操作,但也意味着数组的元素在内存中的位置可能并不连续。

因此,JavaScript的数组并不要求连续的内存区域来存储元素。相反,它们可以根据需要动态地分配和释放内存,以适应不同大小的数组和不同的操作需求。这种灵活性使得JavaScript的数组在处理各种复杂的数据结构和算法时非常有用。

需要注意的是,虽然JavaScript的数组不需要连续的内存区域,但在某些情况下,连续的内存访问可能会带来性能优势。然而,这通常是由JavaScript引擎的优化机制来处理的,而不是由开发者直接控制的。开发者主要关注的是如何使用数组来组织和操作数据,而引擎则会负责底层内存管理的细节。

7.1.4 Array.of()

数组元素传递给Array.of(),来创建并返回新数组。

Array.of()        // => []; 创建一个空数组
Array.of(10)      // => [10]; 
Array.of(1,2,3)   // => [1, 2, 3]

7.1.5 Array.from()

(1)类数组对象转数组

所谓类数组对象就是具有length属性的对象。

需要注意的是,对象中 “整数类型” 属性对应的值才能转为数组的元素。

let arrayLike = { 0: 'a', '1': 'b', 'c': 'c', length: '5' }; 
// 转换为数组  
let arr = Array.from(arrayLike);

在这里插入图片描述

⚠️ ⚠️
1、类数组对象的length需要是数值或数值字符串( 例如 “5.8”,JavaScript会转化为整数5)

2、类数组对象的属性必须是整数或整数字符串才能转为数组元素。(例如: 属性"5",JavaScript会将其转化为整数5) ;

3、Array.from转换时,如果length大于元素个数,JavaScript会将空位部分置为undefined

例:

let obj = {
    1: 1,
    "2": function fun() { console.log("fun"); },
    "4": 4,
    "4.9": 4.9,  // 属性不是整数,不能转换为数组元素
    length: 5.8, // 转换为整数5
};
let arr = Array.from(obj);
console.log(arr);// [undefined, 1, ƒ, undefined, 4]

⚠️ Array.from()方法内部会使用length对数组进行裁剪(见下面的程序),我们将obj中的length改为2,输出:[undefined, 1],以length为准。

let obj = {
   ....
    length: 2,
};
let arr = Array.from(obj);
console.log(arr);// [undefined, 1]

🔗 通过设置length删除数组元素参考7.4节

(2)可迭代对象转数组

可迭代对象包括:1 字符串、2 数组、3 Set、4 Map

let arr =[];
// 1 字符串
arr = Array.from("abc");
console.log(arr);//  ['a', 'b', 'c']
// 2、数组
arr =Array.from([1,2,3]);
console.log(arr);//[1, 2, 3]

// 3、Set
let set = new Set().add(1).add(2).add(3);
arr =Array.from(set);
console.log(arr);//[1, 2, 3]

// 4、Map
let map = new Map().set("one",1).set("tow",2).set("three",3);
arr =Array.from(map);
console.log(arr);//[['one', 1],['tow', 2],['three', 3]

(3)Array.from()的第2个参数:处理函数

处理函数接收两个参数:(数组元素,索引)

let arr1 = [1,3,5,7,9];
let arr2 = Array.from(arr1,(e,i)=>{
	return e>5?e:null;
})
console.log(arr2);// [-1, -1, -1, 7, 9]

稀疏数组,例:

let arr1 = [1,,5,,9];
let arr2 = Array.from(arr1,(e,i)=>{
	return e>5?e:null;
})
console.log(arr2);//  [-1, -1, -1, -1, 9]

在这里插入图片描述
⭐️ 从出的输出结果可以看出,稀疏数组的空位会被遍历。

7.2 读写数组元素

可以使用[]操作符读写数组元素,方括号里的内容称为数组的索引值,合法的索引值是0 ~ 4294967294 ( 2 32 2^{32} 232 -2) 。

(1) 合法的索引值-读写数组

  • 下面是几个常规的写数组元素示例:
let arr = [];
arr[0] = 0;
let one = 1;
arr[0 + one] = 1; // []中可以使用变量、表达式
  • 合法的整数字符串作为索引,与该整数作为索引是等价的,JavaScript会自动将整数字符串转为整数
let arr = [];
arr[0] = 0;
arr["1"] = 1;// 与arr[1] = 1等价

for (let k of arr.keys()){
    console.log(k,typeof k);// 0 'number';1 'number'
}

⚠️ 对象正好相反,会将数值类型的属性转换为字符串类型

let o = {};    // Create a plain object
o[1] = "one";
let keys = Object.keys(o);
for (const k of keys) {
    console.log(k,typeof k);// 1 string
}
  • 合法的索引会让JavaScript自动更新数组的length属性,length = 最大索引 + 1
let arr = [];
arr[100] = 100;// length为101(见下图)

在这里插入图片描述

(2) 非法的索引值- 添加属性&值

  • 如果索引值是非法的,JavaScript就会将其作为普通的对象的属性处理,此时该数组就是一个普通的对象。

示例:

let arr = [];
arr[-1] = -1; // 超出了合法值范围
arr[4294967295] = 4294967295;// 超出了合法值范围 
arr["one"] = "one"; // 索引值非法

在这里插入图片描述

从上图看出length值为0,arr被当成了一个普通对象,添加了3个普通对象的属性(“-1”、“one”、“4294967295”)

7.3 稀疏数组

  • 正常稠密数组是从0开始的连续索引数组,而稀疏数组是一个元素不连续索引的数组。所以稀疏数组的length值是大于数组元素个数的。

中文书中这句话翻译的不对:“稀疏数组就是其元素没有从0开始的索引的数组
A sparse array is one in which the elements do not have contiguous indexes starting at 0。
稀疏数组是一个元素不连续索引的数组,正常数组是从0开始的连续索引数组。

let a = new Array(5); // 创建一个空数组,length设置为5,数组中并没有元素
a = [];               // a引用一个空数组,length设置为0
a[1000] = 0;		  // 数组a的1000索引位置赋值为0,length=1001(索引最大值+1)

逐条执行3条语句a的状态见下面3个图:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

⚠️⚠️ 稀疏数组的空位JavaScript并不会将其设置为undefined/null,用户访问不存在的元素时显示undefined是因为访问了对象不存在的属性。

7.4 数组长度

length可以用来删除数组元素

let a = [1,2,3,4,5]; // 创建一个有5个元素的数组,length自动设置为5
a.length = 3; // length被设置为0,JavaScript只保留前3个元素,变成了[1,2,3]
a.length = 0; // length被设置为0,JavaScript只保留前0个元素,变成了[]空数组,
a.length = 5; // length被设置为5, 数组中并没有元素

7.5 添加和删除数组元素

(1)索引添加

let a = [];      // Start with an empty array.
a[0] = "zero";   // 索引0位置放入一个字符串zero
a[1] = "one"; 	 // 索引1位置放入一个字符串one

在这里插入图片描述

(2)delete置成空位变稀疏(length不变)

let a = [1,2,3];
delete a[1];   // a now has no element at index 2
1 in a         // => false: 索引1位置未定义
a.length       // => 3: delete一个元素不影响数组length,数组变稀疏

在这里插入图片描述

7.6 迭代数组

7.6.1 for-of 迭代数组

(1)迭代数组

let arr = [0,1,2];
arr[10] = 10;
for (const e of arr) {
    console.log(e);
}

for/of使用数组内置的迭代器,按照升序逐个遍历数组的元素。
在这里插入图片描述

(2)迭代数组的entries()和解构赋值

可以使用数组的entries()方法和解构赋值

let arr = [1, 2, 3];
for (const [index, element] of arr.entries()) {
    console.log(typeof index, index, ':', element);
}

输出:
number 0 : 1
number 1 : 2
number 2 : 3

7.6.2 forEach()迭代数组

需要一个函数作为forEach的参数,该函数有3个形参(数组元素,索引,原数组)

let letters = [..."Hello world"];
let uppercase = "";
letters.forEach((element, index,arr) => {  // Note arrow function syntax here 
    arr[index] = element.toUpperCase();
    //console.log(arr);
});
console.log(letters);// ['H', 'E', 'L', 'L', 'O', ' ', 'W', 'O', 'R', 'L', 'D']

7.7 多维数组

JavaScript并不支持真正的多维数组,但我们可以使用数组的数组来模拟

let arrarr = [];
arrarr[0] = [11,12,13];
arrarr[1] = [21,22,23];
console.log(arrarr[1][1]);// 22

7.8 数组方法

7.8.1 数组迭代器方法

本节介绍的方法:
forEach() 、map() 、find()、findIndex()、ervery()、 some()、reduce()、reduceRight()
这些方法会按照顺序,把数组的每个元素传给我们提供的函数。

(1)forEach()

  • 方法的参数是一个函数,函数接收3个参数 (数组元素,索引,原数组),不修改原数组
let data = [1, 2, 3, 4, 5], sum = 0;
// 计算数组元素之和
data.forEach(e=> { sum += e; }); 
console.log("sum:", sum);// sum:15

forEach()方法本身不修改数组,但传递给方法的函数可以修改原数组。

let data = [1, 2, 3, 4, 5], sum = 0;
//递增每个元素的值
data.forEach(function (e, i, a) { a[i] = e + 1;}); // data == [2,3,4,5,6]
console.log(data);// => [2, 3, 4, 5, 6]

(2)map()

map 方法的参数是一个函数,函数接收3个参数 (数组元素,索引,原数组),不修改原数组。

  • map方法会遍历数组中的元素,循环调用传递给map方法的函数,map()返回一个和原数组 “结构相同” 的新数组,但不修改调用它的数组;

  • “结构相同” 是指:“如果原数组是稀疏的,返回的新数组仍然是稀疏的,长度与原数组相同,缺失的元素也相同”

let arr1 = [0, 1, 2];
// map的参数是个函数,该函数接受3个参数(数组元素,索引值,原数组)
let arr2 = arr1.map(function (element, index, array) {
	// map方法会遍历数组中的元素,循环调用本函数
    console.log(element, index, array);
    return element * element;// 返回值作为新数组中的元素
});
console.log(arr1);// [0, 1, 2]
console.log(arr2);// [0, 1, 4]

稀疏数组:

let arr1 = [0, 1, 2];
arr1[5] = 5;// [0, 1, 2, , ,5]
let arr2 = arr1.map(function (element) {
    return element * element;
});
console.log(arr1);//[0, 1, 2, empty × 2, 5]
console.log(arr2);//[0, 1, 4, empty × 2, 25]

在这里插入图片描述

(3)filter()

filter方法的参数是一个函数,函数接收3个参数 (数组元素,索引,原数组),不修改原数组。

filter方法会遍历数组中的元素,循环调用传递给filter方法的函数,filter()返回一个 “ 结构可能不同 ” 的新数组。

let arr = [5, 4, 3, 2, 1];
let arr1 = arr.filter(e => e < 3) // 筛选小于3的元素
console.log(arr);  // [5, 4, 3, 2, 1],fitler并不改变原数组
console.log(arr1); // [2, 1]; 

let arr2 = arr.filter((e, i) => i % 2 === 0)// 筛选索引为偶数
console.log(arr2);// [5, 3, 1]

(4)find()与 findIndex()

find和findeIndex两个方法的参数都是一个函数,函数接收3个参数 (数组元素,索引,原数组),不修改原数组。

  • find方法会遍历数组中的元素,循环调用传递给find方法的函数,找到第一个就会停止,返回第一个匹配上的元素

  • findIndex 和find唯一不同就是返回值,find返回第一个匹配上的元素,findIndex返回第一个匹配上的索引

// 1、找出数组中第一个能被5整除的数
let arr = [1, 20, 3, 40, 3];
let e1 = arr.find(function(e,i,arr){
    return e % 5 === 0;
});
console.log(e1);// 元素值是5的倍数,第一个是20

// 2、找出数组中第一个能被7整除的数
let e2 = arr.find(e => e % 7 === 0);
console.log(e2);// undefined,数组中没有元素值是7的倍数元素,没找到返回undefined

let idx1 = arr.findIndex(e => e === 3)  
console.log(idx1);// 2,第一个等于3的元素索引是2
let idx2 = arr.findIndex(e => e < 0)    
console.log(idx2); // -1; 数组中没有小于0的元素,没找到返回-1

⚠️ 没找到,find返回undefined,findIndex返回-1

(5)every()与some()

every()和some()方法是数组断言函数,方法值接收一个断言函数,函数的参数:(数组元素)。两个方法的返回值都是boolean类型值(true/false;

every()和some()方法内部会调用用户传递过来的断言函数。

(5.1)every()

every()方法会将数组元素依次传递给断言函数。全部为true则返回true(有一个false则立刻返回false)。

let a = [1, 2, 3, 4, 5];
console.log(a.every(x => x < 10));      // => true: 所有元素的只都小于10
console.log(a.every(x => x % 2 === 0)); // => false: 不是所有的元素都是偶数

let r = a.every(function(e){
    console.log(e);//依次输出'1,2,3'
    return e<3;// 遇到3则返回false,后面的元素不用判断
}); 
console.log(r); // false

(5.2)some()

将数组元素依次传递给断言函数,有一个为true则返回true(有一个false则立刻返回false)。

let a = [1, 2, 3, 4, 5];
console.log(a.some(x => x % 2 === 0)); // => true;  有偶数的数组元素
console.log(a.some(isNaN));            // => false; 没有非数字的数组元素
console.log(a.some(largeThan3));       // => true ; 有大于4的数组元素

function largeThan3(v) {
    console.log(v);// //依次输出'1,2,3,4'
    return v > 3; // 遇到4则返回true,后面的元素不用判断
}

(6)reduce()与reduceRight()

reduce()与reduceRight()方法只用我们使用指定的函数,归并数组元素,最终产生一个值

reduce()与reduceRight()方法的参数 : (归并函数 , [初始值])

(6.1)reduce()有初始值

let a = [1, 2, 3, 4];
console.log(a.reduce((x, y) => x + y, 0));// => 15; the sum of the values
/**
 * 参数传递、计算过程:
 * 0,1  -> reduce -> 1  ;  0是初始值作为第1个参数,1数组的第1个元素值,和为1
 * 1,2  -> reduce -> 3  ;  1是上一次计算的结果,2是数组的第2个元素值,和为3
 * 3,3  -> reduce -> 6  ;  3是上一次计算的结果,3是数组的第3个元素值,和为6
 * 6,4  -> reduce -> 10 ;  6是上一次计算的结果,4是数组的第4个元素值,和为10
 * 10,5 -> reduce -> 15 ; 10是上一次计算的想·想··想·结果,5是是数组的第4个元素值,和为15
 */
 
console.log(a.reduce((x, y) => x * y, 1)); // => 120; the product of the values
/**
 * 参数传递、计算过程:
 * 1,1  -> reduce -> 1   ;  1是初始值作为第1个参数,1是数组的第1个元素值,积为1
 * 1,2  -> reduce -> 2   ;  1是上一次计算的结果,2是数组的第2个元素值,积为2
 * 2,3  -> reduce -> 6   ;  2是上一次计算的结果,3是数组的第3个元素值,积为6
 * 6,4  -> reduce -> 24  ;  6是上一次计算的结果,4是数组的第4个元素值,积为24
 * 24,5 -> reduce -> 120 ; 24是上一次计算的结果,5是数组的第4个元素值,积为120
 */

(6.2)reduce()无初始值

let a = [1, 2, 3, 4, 5];
console.log(a.reduce((x, y) => (x > y) ? x : y));// => 5; 
/**
 * 参数传递、计算过程:
 * 1,2  -> reduce -> 2  ;  1是数组的第1个元素值,2是数组的第2个元素值,大的数为2
 * 2,3  -> reduce -> 3  ;  2是上一次比较的结果,3是数组的第3个元素值,大的数为3
 * 3,4  -> reduce -> 4  ;  3是上一次比较的结果,4是数组的第4个元素值,大的数为4
 * 4,5  -> reduce -> 5  ;  4是上一次比较的结果,5是数组的第5个元素值,大的数为5
 */
 
console.log(a.reduce((x, y) => x + y)); // => 15; the sum of the values
/**
 * 参数传递、计算过程:
 * 1,2  -> reduce -> 3  ;  1是数组的第1个元素值,2是数组的第2个元素值,和为3
 * 3,3  -> reduce -> 6  ;  3是上一次计算的结果,3是数组的第3个元素值,和为6
 * 6,4  -> reduce -> 10 ;  6是上一次计算的结果,4是数组的第4个元素值,和为10
 * 10,5 -> reduce -> 15 ; 10是上一次计算的结果,5是数组的第5个元素值,和为15
 */

(6.3)reduceRight()有初始值

reduceRight()与reduce()类似,只不过reduceRight是从高索引向低索引处理数组

let a = [1, 2, 3];
let val = a.reduceRight(function (acc, val) {
    console.log(acc, val);
    return Math.pow(acc, val)
}, 4); // => 4096
console.log(val);
/**
 * 参数传递、计算过程:
 *    4,3  -> reduce -> 64   ; 4是初始值作为第1个参数,3数组的倒数第1个元素值,4的3次方为64
 *   64,2  -> reduce -> 4096 ; 64是上一次计算的结果,2是数组的倒数第2个元素值,64的3次方为4096
 * 4096,1  -> reduce -> 4096 ; 4096是上一次计算的结果,1是数组的倒数第3个元素值,4096的1次方为4096
 */

(6.3)reduceRight()无初始值

reduceRight()与reduce()类似,只不过reduceRight是从高索引向低索引处理数组

let a = [1, 2, 3];
let val = a.reduceRight(function (acc, val) {
    console.log(acc, val);
    return Math.pow(acc, val)
}) // => 9
console.log(val);
/**
 * 参数传递、计算过程:
 *    3,2  -> reduce -> 9 ; 3是数组的倒数第1个元素值,2是数组的倒数第2个元素值,3的2次方为9
 *    9,1  -> reduce -> 9 ; 9是上一次计算的结果,1是数组的倒数第3个元素值,9的1次方为9
 */

7.8.2 使用flat()和flatMap()打平数组

(1)flat

打平数组中的数组类型的元素,例:

let arr = [1, [2, [3]]].flat()
console.log(arr);// => [1, 2, [3]]

从输出看出,数组中的数组[2, [3]] ,只被 “剥”开了1层,也称打平了1层。

通过参数可以设定打平的层数,例

let arr = [1, [2, [3]]].flat(2);
console.log(arr);// => [1, 2, 3] : 元素是数组的最深只有2层

arr = [1, [2, [3]]].flat(3);
console.log(arr);// => [1, 2, 3] : 元素是数组的最深只有2层,参数>2效果和2一样

(1.1)空数组打平后就消失了

let arr1 = [[],[]]
console.log(arr1.length);// 2
let arr2 = arr1.flat();
console.log(arr2.length);// 0

(2)模拟flat实现

下面模拟flat实现的代码实现可以参考一下,体会一下数组元素中存在数组 && 还没有达到用户设定的打平层数,则继续打平1层

let _arr = new _Array([1, [2, [3]]]);
console.log(_arr._flat());// [1, 2, [3]]
console.log(_arr._flat(2)); // [1, 2, 3]

function _Array(vals) {
    this.vals = vals;
    this._flat = function (n) {
        let existsElementIsArray = false, flat_counter = 1, flatArr = _flat1(this.vals);
        /* 数组元素中存在数组 && 还没有达到用户设定的打平层数,则继续打平1层 */
        while (existsElementIsArray && flat_counter <= n) {
            flatArr = _flat1(flatArr);
        }
        return flatArr;

        /* 打平一层 */
        function _flat1(arr) {
            flat_counter++;
            let _arr = []
            for (const e of arr) {
                if (Array.isArray(e)) {//打平1层放入_arr
                    for (const _e of e) {
                        existsElementIsArray = Array.isArray(_e);
                        _arr.push(_e)
                    }
                } else {
                    _arr.push(e)
                }
            }
            return _arr;
        }
    }
    return this;
};

(2)flatMap

这个方法名叫做mapFlat更合适,该方法其实是先map再flat,且flat只打平1层。

(1)先map再flat

let phrases = ["hello world", "the definitive guide"];
let words = phrases.flatMap(phrase => phrase.split(" "));
console.log(words); // => ["hello", "world", "the", "definitive", "guide"];

(2)空数组打平后就消失了

let arr = [-2, -1, 1, 2].flatMap(x => x < 0 ? [] : Math.sqrt(x));
console.log(arr);// [1, 1.4142135623730951]

7.8.3 使用concat()添加数组

concat(新元素1 , .... , 新元素n),原数组拼接参数中给出的新元素,如果新元素是数组,则打平1层后再进行拼接。

let arr = [1, 2, 3];
arr.concat(4, 5)          // => [1,2,3,4,5]
arr.concat([4, 5], [6, 7])   // => [1,2,3,4,5,6,7]; 拼接的是数组,打平1层进行拼接
arr.concat(4, [5, [6, 7]])  // => [1,2,3,4,5,[6,7]]; 拼接的是数组,打平1层进行拼接
console.log(arr); // [1, 2, 3] ,原数组没有改变

7.8.4 push(), pop(),shift()和unshift()实现栈和队列操作

push(), pop(),shift()和unshift()改变原数组

添加删除
数组尾部操作push()pop()
数组头部操作unshift()shift()

在这里插入图片描述

console.log("---------push---------");
let arr = [];
console.log(arr.push(1,2));      //=> 2
console.log(arr);                //=> [1, 2]
console.log(arr.push(3,4));      //=> 4
console.log(arr);                //=> [1, 2, 3, 4]

console.log("--------- pop --------");
console.log(arr.pop());          //=> 4, 返回pop前的数组长度
console.log(arr.length);         //=> 3
console.log(arr);                //=> [1, 2, 3]

console.log("------- unshift ------");
arr = [];
console.log(arr.unshift(1,2));   //=> 2
console.log(arr);                //=> [1, 2]
console.log(arr.unshift(3,4));   //=> 4
console.log(arr);                //=> [3, 4, 1, 2]

console.log("-------- shift -------");
console.log(arr.shift());        //=> 3, 返回shift后的数组长度
console.log(arr);                //=> [4, 1, 2]

4个操作都会返回数组的长度;
⭐️ 比较特殊的是pop()函数返回的是pop前的数组长度,其他三个函数都是返回操作后的数组长度

7.8.5 使用slice()、splice()、fill()、copyWithin()

slice /slaɪs/ v. 把……切成薄片;切
splice /splaɪs/ v. 拼接

(1)slice([开始索引(0)],[结束索引(length)])

⭐️ 不修改原数组。
说明:slice([开始索引(0)],[结束索引(length)]) ,提取参数指定位置范围内元素组成的数组。
返回:返回指定位置范围内元素组成的数组

如果指定的索引是负数,则表示倒数的位置。

let arr = [0, 1, 2, 3, 4];
console.log(arr.slice());       // [0, 1, 2, 3, 4],返回0~length-1
console.log(arr.slice(0, 3));   // [0, 1, 2],返回索引位置"0~2"的元素组成的数组
console.log(arr.slice(3));      // [3, 4],返回索引位置"3~最后"的元素组成的数组
console.log(arr.slice(1, -1));  // [1, 2, 3],返回索引位置"1~倒数第1前"的元素组成的数组
console.log(arr.slice(-3, -2)); // [2] ,返回索引位置"倒数第3~倒数第3(倒数第2前)"的元素组成的数组
console.log(arr);               // [0, 1, 2, 3, 4] ,不影响原先的数组

(2)splice 删除&插入

⭐️ splice 修改了原数组。

说明:splice(删除起始索引,[删除的长度]), splice删除参数指定范围的数组元素。如果省略删除长度,则从起始索引开始到数组末尾的元素都会被删除。

返回:返回被删除元素组成的数组。

(2.1)指定位置、指定长度删除

splice(删除起始索引,[删除的长度(默认值length)])

let arr1 = [1, 2, 3, 4, 5, 6, 7, 8];
console.log(arr1.splice(4)); // [5, 6, 7, 8]:默认长度数组的length
console.log(arr1); // [1, 2, 3, 4]
console.log(arr1.splice(1, 2)); // [2,3]
console.log(arr1); // [1,4]
console.log(arr1.splice(1, 1));// [4]
console.log(arr1);// [1]

(2.2)删除再插入

splice(删除和插入的起始索引,删除的长度 , ...插入的数据), 插入位置和插入位置后面的数组元素会后移。

let arr2 = [1, 2, 3, 4, 5];
console.log(arr2.splice(2, 0, "a", "b"));// []: 删除长度为0-未删除任何元素
console.log(arr2);// [1,2,"a","b",3,4,5]

console.log(arr2.splice(2, 2, [1, 2], 3));// ["a","b"]
console.log(arr2);// [1,2,[1,2],3,3,4,5]

(3)fill(指定的值,[起始索引(0)],[结束索引(length)])

⭐️ fill 修改了原数组。

说明:fill(指定的值,[起始索引(0)],[结束索引(length)]),使用指定的值,填充数组指定位置的内容。

返回:返回修改后的数组。

let a = new Array(5);
a.fill(0)               // => [0,0,0,0,0]; 未指定起始、结束索引,则整个数组元素被设置为0
a.fill(9, 1)            // => [0,9,9,9,9]; 未指定结束索引,则从起始索引到末尾都被设置为9
a.fill(8, 2, -1)        // => [0,9,8,8,9]; 索引2位置到倒数第1前的数组元素被设置为8

(4)copyWithin(被覆盖起始索引,[复制起始索(0)],[复制终止索引(length)])

⭐️ copyWithin 修改了原数组。

说明:copyWithin(被覆盖起始索引,[复制起始索(0)],[复制终止索引(length)]),复制“起始索引” 到 “终止索引” 前的元素被复制,从被覆盖起始索引处开始覆盖原数组元素。

返回:修改后的数组

例1:

let arr = [1, 2, 3, 4, 5];
let _arr = arr.copyWithin(2, 3, 5);
console.log(_arr == arr);// true
console.log(arr);//[1,2,4,5,5]

在这里插入图片描述

例2:

let a = [1,2,3,4,5];
a.copyWithin(1); // => [1,1,2,3,4]: 起始索引默认0,终止索引默认值为length
a.copyWithin(2, 3, 5); // => [1,1,3,4,4]
a.copyWithin(0, -2)   // => [4,4,3,4,4]

a.copyWithin(1) 超出原有数组长度的部分会被忽略
在这里插入图片描述

7.8.6 查找与排序

(1)indexOf

⭐️ indexOf不修改原数组。

说明: indexOf(搜索内容 , [开始索引]), 从前到后在数组中搜索参数指定的内容
返回: 第一个匹配到的元素在数组中的索引值。未找到返回-1

let a = [0, 1, 2, 1, 0];
console.log(a.indexOf(0));      // => 0
console.log(a.indexOf(3));      // => -1

console.log(a.indexOf(0,1));    // => 4,

(2)lastIndexOf

⭐️ lastIndexOf不修改原数组。

说明: lastIndexOf(搜索内容 , [开始索引]) , 从后向前在数组中搜索参数指定的内容

返回: 第一个匹配到的元素在数组中的索引值。

let a = [0, 1, 2, 1, 0];
console.log(a.lastIndexOf(0));    // => 4: 由后向前,第一个为0的是a[4]
console.log(a.lastIndexOf(0,-2)); // => 0: 由后向前,从倒数第2个开始找,第一个为0的是a[0]

(3)indexOf的应用 - 元素在数组中的所有位置索引

let a = [,2,3,2];
function findall(a, x) {
    let results = [],            // The array of indexes we'll return
        len = a.length,          // The length of the array to be searched
        pos = 0;                 // The position to search from
    while (pos < len) {           // While more elements to search...
        pos = a.indexOf(x, pos); // Search
        if (pos === -1) break;   // If nothing found, we're done.
        results.push(pos);       // Otherwise, store index in array
        pos = pos + 1;           // And start next search at next element
    }
    return results;              // Return array of indexes
}
console.log(findall(a,2));

简化了一下:

let a = [,2,3,2];
function _findall(a, x) {
    let results = [], pos = 0;
    while (true) {
        pos = a.indexOf(x, pos);
        if (pos === -1) break;
        results.push(pos);
        pos++;
    }
    return results;
}
console.log(_findall(a,2)); //

(3)includes

⭐️ includes不修改原数组。

说明: includes(搜索内容) ,是否包含搜索的内容

返回: true/false

例:

let arr1 = ["hello"];
let arr2 = arr1;
let arr = [[],[],arr1,[],NaN];
console.log(arr.indexOf(arr2));  // 2
console.log(arr.includes(arr2)); // true

console.log(arr.indexOf([]));  // -1
console.log(arr.includes([])); // false

console.log(arr.indexOf(NaN));   // -1  , indexOf相等性测试使用的是`===`,无法判断NaN
console.log(arr.includes(NaN));  // true, includes可以测试是否包含NaN

注意NaN

(4)sort(function(a,b){…}) 排序

⭐️ sort 修改了原数组。

说明: sort(function(a,b){...;return 正数/负数}) 对数组进行排序,正序排序则比较函数返回值使用 return a-b; ,倒序排序则比较函数返回值使用return b-a;。排序是稳定的,即相等的两个元素顺序不改变。
返回: 排序后的数组

let arr = [1,4,,36,2]
console.log(arr);// [1, 4, empty, 36, 2]
arr.sort();
// 按字母顺序排列,未定义的元素,它们会被排到数组末尾
console.log(arr);// [1, 2, 36, 4, empty]
arr.sort((a,b)=>{
    return a-b;
});
console.log(arr);//  [1, 2, 4, 36, empty]

(5)sort排序算法

排序算法采用的是快速排序,把数组的后一个元素传递给比较参数的第一个参数,前一个元素传递给比较函数的第二个参数,如果返回值是正数则不交换位置,负数则交换位置。


(6)reverse 反转数组顺序

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

7.8.7 数组到字符串的转换

Array类定义了 3个把数组转换为字符串的方法:join、toString、toLocalString

(1)join

把数组中所有的元素转换为字符串,按顺序依次拼接成一个字符串。

参数: 1个可选参数,用来分隔拼接的字符串。(默认值为逗号 “,”
返回值:拼接后的字符串
原数组:不变,改变

let arr = [1, 2, 3];
arr.join()               // => "1,2,3"
arr.join(" ")            // => "1 2 3"
arr.join("")             // => "123"

⚠️稀疏数组的空位会以空字符串参与拼接

let arr = new Array(5);
console.log(arr.join("-"));// => "----" 4个分隔符(n个元素有n-1个分隔符)
console.log(arr.join());   // => ",,,,"

(1)toString()

无参数,与没有参数的join方法一样(采用逗号分隔)

(1)toLocalString()

无参数,考虑本地化的格式

let arr = [new Date(),"hello"];
console.log(arr.toString());// Sun Apr 07 2024 21:59:12 GMT+0800,hello
console.log(arr.toLocaleString());// 2024/4/7 21:59:12,hello

7.8.8 静态数组函数

Array有 3个静态函数:Array.of()、Array.from()、Array.isArray()

console.log(Array.isArray([]));  // true
console.log(Array.isArray({}));  // false
console.log(Array.isArray(1));   // false
console.log(Array.isArray("1")); // false
console.log(Array.isArray({length:1,a:"a"})); // false 类数组对象仍然是false

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

⚠️ typeof 并不能判断一个对象是否是数组

非原始类型(如对象、数组、函数、null等),typeof 都会返回 “object”

7.9 类数组对象

let obj = {
    1: 1,
    "2": 2,
    "f": function fun() { },
    length: 5,
};
let arr = Array.from(obj);
console.log(arr);// [undefined, 1, 2, undefined, undefined]

7.10 作为数组的字符串

  • 字符串不是数组;
  • 字符串可以使用[]来获取某个字符,可完全替代chatAt方法
let s = "test";
console.log(s['0']);// => "t",可以使用整数字符串
console.log(s[1]);// => "e"
  • 字符串与数组的行为类似,也意味着我们可以对字符串使用泛型的字符串方法
let str = Array.prototype.join.call("JavaScript", "-")
console.log(str);//J-a-v-a-S-c-r-i-p-t
str = Array.prototype.join.call("JavaScript")
console.log(str);//J,a,v,a,S,c,r,i,p,t

7.11 小结

数组比普通对象特殊的地方:

  • 1、数据按索引值升序排列
  • 2、自动维护length值,
  • 3、可以通过设置length值来删除元素
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

java硕哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值