JavaScript 数组是 Web 开发中最常用的数据结构之一,它为我们提供了一种更加方便、灵活的方式来组织和操作数据。本文主要从数组的功能使用的角度出发,旨在为读者提供一个系统、易懂的 JavaScript 数组总结。
ECMAScript 数组跟其他编程言的数组有很大区别。跟其他语言中的数组一样,ECMAScript 数组也是一组有序的数据,但跟其他语言不同的是,数组中每个槽位可以存储任意类型的数据。这意味着可以创建一个数组,它的第一个元素是字符串,第二个元素是数值,第三个是对象。ECMAScript 数组也是动态大小的,会随着数据添加而自动增长。
1、使用数组字面量
const arr1 = [] // 创建一个空数组
const arr2 = ['lily','lucy','Tom']; // 创建一个包含3个字符串的数组
2、使用构造函数
// 1.无参构造
const arr1 = new Array(); // 创建一个空数组
// 2.带参构造
/* 如果只传一个数值参数,则表示创建一个初始长度为指定大小的数组 */
const arr2 = new Array(10); // 创建一个包含10项且初始值为undefined的数组
/* 如果传入一个非数值的参数或者参数大于1,则表示创建一个包含指定元素的数组 */
const arr3 = new Array('lily','lucy','Tom'); // 创建一个包含3个字符串的数组
3、使用Array.from方法创建数组 ES6新增
Array.from() 静态方法从或对象创建一个新的浅拷贝的数组实例。
// 在 JavaScript 中,所有的内置集合(如数组、Map、Set等)都是可迭代的
const arr = Array.from('foo');
console.log(arr);// ["f", "o", "o"]
const arr = Array.from([1,2,3],(x) => x + x))
console.log(arr);// [2, 4, 6]
4、使用Array.of方法创建数组 ES6新增
Array.of() 静态方法通过可变数量的参数创建一个新的 Array 实例,而不考虑参数的数量或类型。
Array.of 方法的引入使得创建数组更加直观和易于理解,消除了使用数组字面量和 Array 构造函数时的一些潜在歧义。
// Array.of(element0, element1, /* … ,*/ elementN)
const arr1 = new Array(5) // 创建一个包含5项且初始值为undefined的数组
const arr2 = new Array(5,2) // 创建一个包含元素5和2的数组
const arr3 = Array.of(5) // 创建一个包含元素5的数组
const arr4 = Array.of(5, 2); // 创建一个包含元素5和2的数组
5、通过拷贝其它已有数组
需要注意深、浅拷贝的问题。
/* 这里顺便讲一下数组浅拷贝的几种方法 */
const arr = [1,2,3]
// 方法一 使用es6新增的扩展运算符
const arr1 = [...arr]
// 方法二 使用数组中返回一个新数组的方法,如map、concat、from等方法。
const arr2 = arr.concat()
// 方法三 将数组序列化为JSON 字符串,然后再将JSON 字符串反序列化为数组。
const arr3 = JSON.parse(JSON.stringify(arr))
// splice(start, deleteCount, item1, item2, /* …, */ itemN)
let arr = [1,2,4,5];
arr.splice(2,0,3);// 不删除只插入
console.log(arr); // [1,2,3,4,5]
/* splice()方法可以在原数组上移除或者替换已存在的元素,或添加新的元素。 */
// splice(start, deleteCount)
let arr = [1,2,3,'1','2'];
arr.splice(3);// 从下标3开始,包括star,一直删除到数组末尾
console.log(arr);// [1,2,3]
arr.splice(1,1);// 从下标1开始,包括star,删除一个元素
console.log(arr);// [1,3]
:返回一个新的数组对象,这一对象是一个由 start 和 end 决定的原数组的(包括 start,不包括 end),其中 start 和 end 代表了数组元素的索引。原始数组不会被改变。
slice() 全部截取
slice(start) 从start开始,包括start,截取到末尾
slice(start,end) 从start开始,包括start,不包括end
负索引从数组末尾开始计算
const arr = [0,1,2,3,4];
console.log( arr.slice() );//[0,1,2,3,4]
console.log( arr.slice(1) );// [1,2,3,4]
console.log( arr.slice(1,3) );// [1,2]
!需要注意的一点是slice方法会对原数组上的引用类型采取浅拷贝的方式。!
/* 浅拷贝 */
const arr = [0,{key:'value'},['a']];
const arr_slice = arr.slice();//不写参数代表全部截取
arr_slice.push(1);
arr_slice[1].key = 'changed';
arr_slice[2].push('b');
console.log(arr);// [0,{key:'changed'},['a','b']];原数组的引用类型被修改
console.log(arr_slice);// [0,{key:‘changed’},[‘a’,‘b’],1];
splice(start)
splice(start, deleteCount)
splice(start, deleteCount, item1, item2)
splice(start, deleteCount, item1, item2, /* …, */ itemN)
1. 负索引从数组末尾开始计算
2. 如果start大于数组长度,则表现为在数组末尾添加元素
let arr = [1,2,2,4];
// splice(start , deleteCount , item1 , item2 , … )
arr.splice(2,1,3); // 删除且插入相同数量的元素,达到替换若干元素的效果
console.log(arr);// [1,2,3,4]
let arr = new Array(10).fill(0); // fill(value, start, end) start和end可省略
console.log(arr);// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
indexOf使用严格相等( === ) 比较,NAN的值永远不会被比较为相等,此外它还会跳过中的空槽。
// find(callbackFn, thisArg);
const arr = [5, 12, 8, 130, 44];
const foundedValue = array1.find((element) => element > 10);
console.log(found);// 12
Array.prototype.findIndex(callbackFn, thisArg);
Array.prototype.includes(searchElement, fromIndex); // 包括fromIndex
Array.prototype.some(callbackFn, thisArg)
const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];
const array3 = array1.concat(array2);
console.log(array3);// ["a", "b", "c", "d", "e", "f"]
const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];
const array3 = [...array1 , ...array2];
console.log(array3);// ["a", "b", "c", "d", "e", "f"]
Array.prototype.reverse() // 要在不改变原始数组的情况下反转数组中的元素,使用 toReversed()。 // 要实现原有功能,但不改变原数组的to系列还有toSorted()、toSpliced()
sort()
sort(compareFn)
sort()方法保留空槽,如果原数组是稀疏的,空槽会被移动到数组末尾,并且排在undefined后面
/*
若返回值大于 0 a 在 b 后,如 [b, a]
若返回值小于 0 a 在 b 前,如 [a, b]
若返回值全等于 0 保持 a 和 b 原来的顺序
*/
function compareFn(a, b) {
return b - a ; // 升序排序
}
let arr = [2,1,3];
arr.sort();
console.log(arr); // [1,2,3]
arr.sort(compareFn);
console.log(arr); // [3,2,1]
!sort默认的排序方法是将元素转换为字符串,然后按照它们的 UTF-16 码元值升序排序。在数值排序中,9 本该出现在 80 之前,但是排序过程中数字会被转换为字符串,而在 Unicode 顺序中“80”排在“9”之前,所以会出现80排在9之前的情况。此外所有的 undefined 元素还会被排序到数组的末尾。因此最好使用自定义的compare函数规定排序规则。 !
const array = [1, 30, 4, 21, 100000];
array.sort();
console.log(array); // [1, 100000, 21, 30, 4]
array.sort((a,b) => a - b)
console.log(array); // [1, 4, 21, 30, 100000]
let arr = ['你','好'];
const str = arr.join('-');
console.log(str);// 你-好
arr = str.split('-');
console.log(arr);// ['你','好']
// toLocaleString(locales, options)
// locales 带有 BCP 47 语言标签的字符串,或者此类字符串的数组。
// 一个 BCP 47 语言标记代表了一种语言或者区域。
// options 分隔符,默认为 ' , '
const array1 = [1, 'a', new Date('21 Dec 1997 14:12:00 UTC')];
const localeString = array1.toLocaleString('en', { timeZone: 'UTC' });
console.log(localeString);// "1,a,12/21/1997, 2:12:00 PM",
/*
reduce(callbackFn)
reduce(callbackFn, initialValue)
callbackFn的返回值将作为下一次调用 callbackFn 时的 pre 参数。对于最后一次调用,返回值将作为 reduce() 的返回值。
callbackFn的参数包括:
pre:上一次执行callbackFn的返回值,如果是第一次执行则会取initialValue,如果没有指定initialValue,则为 array[0] 的值。
cur:当前元素的值。在第一次调用时,如果指定了 initialValue,则为 array[0] 的值,否则为 array[1]。
curIndex:当前元素的值。在第一次调用时,如果指定了 initialValue,则为 array[0] 的值,否则为 array[1]。
array: 调用了 reduce() 的数组本身。
*/
// **** 如果数组为空且未提供 initialValue 初始值,则会抛出异常。****
const array1 = [1, 2, 3, 4];
// 使用reduce函数进行数组元素求和
const initialValue = 0; // 初始值
const sum = array1.reduce((pre, cur) => pre + cur , initialValue);
console.log(sum);// 10
普通的遍历数组的方法除了for循环、forEach方法和map方法以外,数组还能使用for...of(ES6新增) 进行遍历。for...of 循环语句的原理是对可迭代对象进行遍历。可迭代对象是指实现了迭代器接口(Iterator Protocol)的对象,它们包括数组、字符串、Set、Map 等。在 JavaScript 中,所有的内置集合都是可迭代的,因此 for...of 循环语句可以用来遍历这些内置集合。
数组提供了几个返回对象的方法:
const array1 = ['a', 'b', 'c'];
const iterator = array1.entries();
console.log(iterator); // Iterator: {}
console.log(iterator1.next().value); // [0, "a"]
数组的扁平化其实就是将一个嵌套多层的数组转换为只有一层的数组。
实现方法:
1. 递归方法: 使用递归函数来遍历数组,将嵌套数组的元素逐个添加到结果数组中,递归的思路有很多种,可以使用concat、reduce、扩展运算符和some等。
2. 使用 flat() 方法: 使用数组的 flat() 方法可以将嵌套数组扁平化为指定深度的一维数组。
flat(depth)
depth 为指定要提取嵌套数组的结构深度,默认值为 1,参数也可以传进Infinity,代表不论多少层都要展开。
3. split 和 toString 共同处理
4. 正则和 JSON 方法共同处理
5. 等等
* 由于篇幅问题,这里不再展开讨论,这里参考的是csdn的一篇文章,感兴趣的小伙伴可以前往查看或者网上查阅资料。
1. 通过Array.isArray()方法,该方法还支持对iframe判断是否是数组。ES6新增
const a = 2;
console.log( Array.isArray(a)); // false
2. 通过instanceof 运算符(不准确)
instanceof 底层原理是检测构造函数的 prototype 属性是否出现在某个实例的原型链上,如果实例的原型链发生变化,则无法做出正确判断。此外如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的Array构造函数。如果你从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。
const arr = [];
console.log( arr instanceof Array ); // true
3.通过Object.prototype.toString.call(判断对象)(通用)
const obj = {};
const arr = [];
const res1 = Object.prototype.toString.call(obj); // 返回的结构 "[object 类型]"
const res2 = Object.prototype.toString.call(arr); // 返回的结构 “[object 类型]”
console.log(res1); // "[object Object]"
console.log(res2); // “[object Array]”
4.原型链方法(不准确)
缺点和第二种方法一样
const a = {};
// 1. 通过 Array.prototype.isPrototypeOf
const res = Array.prototype.isPrototypeOf(a);
// 2. 通过 判断对象.constructor == Array
const res = a.constructor == Array;
定型数组(typed array)是ECMAScript新增的结构,目的是提升向原生库传输数据的效率。实际上,JavaScript并没有“TypedArray”类型,它所指的其实是一种特殊的包含数值类型的数组。
1.在JavaScript中,所有的 Number 类型都是采用双精度浮点数(位)来表示的,即占用8个字节。这意味着在JavaScript中,无论是整数还是浮点数,都使用相同的内部表示形式。JavaScript引擎通常会使用位双精度浮点数来存储数字值。(gpt3.5)
2.在 WebGL 的早期版本中,因为 JavaScript 数组与原生数组之间不匹配,所以出现了性能问题。图形 驱动程序 API 通常不需要以 JavaScript 默认双精度浮点格式传递给它们的数值,而这恰恰是 JavaScript 数组在内存中的格式。因此,每次 WebGL 与 JavaScript 运行时之间传递数组时,WebGL 绑定都需要在目标环境分配新数组,以其当前格式迭代数组,然后将数值转型为新数组中的适当格式,而这些要花费很多时间。
这当然是难以接受的,Mozilla 为解决这个问题而实现了 CanvasFloatArray。这是一个提供 JavaScript 接口的、C 语言风格的浮点值数组。JavaScript 运行时使用这个类型可以分配、读取和写入数组。这个数组可以直接传给底层图形驱动程序 API,也可以直接从底层获取到。最终,CanvasFloatArray 变成了 Float32Array,也就是今天定型数组中可用的第一个“类型”。
在揭开定型数组的面纱之前,我们还需要了解两个概念---ArrayBuffer和视图。
上面提到的Float32Array 实际上是一种“视图”,它允许 JavaScript 运行时访问一块名为 ArrayBuffer 的 预分配内存。ArrayBuffer 是所有定型数组及视图引用的基本单位。实际上,ArrayBuffer()是一个普通的 JavaScript 构造函数,可用于在内存中分配特定数量的字节空间,而且ArrayBuffer 一经创建就不能再调整大小。
const buf = new ArrayBuffer(16); // 在内存中分配 16 字节
alert(buf.byteLength); // 16
ArrayBuffer 某种程度上类似于 C++的 malloc(),但也有几个明显的区别。
我们不能仅通过对 ArrayBuffer 的引用就读取或写入其内容,要读取或写入 ArrayBuffer,就必须通过视图。视图有不同的类型,但引用的都是 ArrayBuffer 中存储的二进制数据。
想要使用DataView,首先需要创建一个ArrayBuffer实例,然后用这个实例来创建新的Dataview。关于DataView的具体操作这里就不做过多介绍了,感兴趣的同学可以去网上搜集资料,这里主要是为了引出定型数组这一概念。
定型数组是另一种形式的 ArrayBuffer 视图。虽然概念上与 DataView 接近,但定型数组的区别在于,它特定于一种 ElementType 且遵循系统原生的字节序。相应地,定型数组提供了适用面更广的 API 和更高的性能。设计定型数组就是为了提高与 WebGL 等原生库交换二进制数据的效率。由于定型数组的二进制表示对操作系统而言是一种容易使用的格式,JavaScript 引擎可以重度优化算术运算、 按位运算和其他对定型数组的常见操作,因此使用它们速度极快。
TypeArray构造函数是所有TypeArray子类(Uint8Array、Float32Array等)的通用父类。将 TypeArray作为一个“抽象类”,其为所有类型化数组的子类提供了实用方法的通用接口。该构造函数没有直接暴露:没有全局的 TypeArray属性。它只能通过Object.getPrototypeOf(Int8Array) 及类似方式访问。
在我看来,ArrayBuffer的本质是javaScript的一个普通的构造函数,它的作用是在内存中分配特定数量的字节空间,从而创建数组缓冲区 ,而视图(view)则是 JavaScript 中处理底层二进制数据(数组缓冲区)的一类特殊对象,它们提供了一种特定的视角或表示方式,用于直接操作底层的二进制数据,而无需对数据进行复制或转换。这些视图对象包括 TypedArrays(如 Float32Array、Int32Array 等)和DataView。它们具有一些特殊的属性和方法,可以让您以特定的数据类型来访问和操作底层的二进制数据。这些属性和方法包括读取和写入特定类型的数据、获取数据的长度和字节顺序、处理数据的偏移量等。
JavaScript 数组提供了丰富的方法和操作,能够满足各种需求,是 Web 开发中不可或缺的重要工具之一
文中提到的操作方法主要基于javascript的原生功能,对于实际开发过程,开发人员可以结合自己的算法思想和其它数据结构等等,对数组的操作方法进行进一步的封装和组合,达到更加高效,个性化的目的。
本文主要从功能使用的角度总结数组的知识点,对于更加深入的底层结构并没有涉及,如果有疏漏的地方还请读者见谅
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- gamedaodao.com 版权所有 湘ICP备2022005869号-6
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务