JavaScript 数组特性太多?一文速通数组容器常见实例方法
前言刷题时为数组的push、pop、shift、unshift等方法的区别而困惑为数组容器特性抓耳挠腮时不时翻看教程文档没有形成自己的体系看 JavaScript 算法题解对于 ES6 特性比较陌生没有头绪本文根据博主自身的刷题经验系统总结了数组容器常见的实例方法涵盖栈与队列操作、数组与字符串互转、双指针技巧、自定义排序以及遍历方法等核心场景。读完本文你将能够熟练运用push/pop/unshift/shift实现栈和队列分清join和split的用法与区别掌握双指针与自定义排序的实战技巧理解map与forEach的适用场景。栈和队列的经典操作学完数据结构后我们都知道栈和队列的基本特性。如栈是后进先出LIFO队列是先进先出FIFO。但是落实到不同的编程语言中对于栈和队列的基本操作可能就不尽相同。那么在原生 JavaScript 中有没有方法能实现栈和队列的基本操作呢答案是有的。栈的基本操作在介绍栈的基本操作前需要先分别介绍数组的push方法和pop方法push方法数组末尾添加元素push方法可以在数组末尾添加元素其中要注意的是push方法可以添加多个元素。返回的参数是数组添加元素后数组的长度。const arr []; arr.push(1); // 1返回添加元素后数组长度 arr; // [1] // 可以在数组末尾添加多个元素 arr.push(true, {}); // 3返回添加元素后数组长度 arr; // [1, true, {}]以下是博主自己总结的push方法伪代码若看不懂可以跳过// 数组结构定义 const Array function () { this.array []; this.element null; }; /** * return {number} */ // 返回数组长度 Array.prototype.size function () { return this.array.length; }; /** * param {element} element * return {number} */ // 函数括号内的 ... 表示 rest 参数用于获取函数的多余参数结果存放在数组中 Array.prototype.push function (...element) { // for ... of 循环遍历所有数组元素 for (const elem of element) { this.array.push(elem); // 在数组末尾添加元素 } return this.size(); // 返回添加新元素后的数组长度 };pop方法数组末尾删除元素pop方法用于删除数组的最后一个元素并返回该元素。const arr [a, b, c]; arr.pop(); // c返回弹出的元素 arr; // [a, b]其中要注意若数组为空对数组的pop操作会返回undefined[].pop() // undefinedpush方法和pop方法的结合构成了后进先出LIFO的栈结构const arr []; arr.push(1, 2); // 2添加元素后数组长度 arr; // [1, 2] arr.push(3); // 3添加元素后数组长度 arr; // [1, 2, 3] arr.pop(); // 3弹出的元素 arr // [1, 2]队列的基本操作上述push和pop方法针对的都是数组末尾增删元素的场景。下文则针对数组起始位置增删元素的场景unshift方法数组首位的 push 操作unshift方法用于在数组的第一个位置添加元素并返回添加新元素后的数组长度。类比数组的push方法相当于push方法在数组末尾添加元素unshift方法在数组开头添加元素二者互为关联。const arr [a, b, c]; arr.unshift(x); // 4 arr; // [x, a, b, c]数组开头添加元素 arr.push(y); // 5 arr; // [x, a, b, c, d]数组末尾添加元素同样unshift方法也能添加多个元素。const arr [a, b, c]; arr.unshift(x, y); // 5 arr; // [x, y, a, b, c]数组开头添加多个元素 arr.push(m, n); // 7 arr; // [x, y, a, b, c, m, n]数组末尾添加多个元素shift方法数组首位的 pop 操作shift方法用于删除数组的第一个元素并返回该元素。类比数组的pop方法相当于pop方法在数组末尾删除元素shift方法在数组开头删除元素二者互为关联。const arr [a, b, c]; arr.shift(); // a开头弹出的元素 arr; // [b, c] arr.pop(); // c末尾弹出的元素 arr; // [b]push方法和shift方法的结合构成了先进先出FIFO的队列结构const arr []; arr.push(1, 2); // 2 arr; // [1, 2] arr.push(3); // 3 arr; // [1, 2, 3] arr.shift(); // 1弹出的元素 arr // [2, 3]对于以上实例方法是否改变原数组博主是这么理解的在栈和队列中的基本操作中无论是弹入还是弹出元素均会影响栈和队列容器内的元素。所以对于push/pop/unshift/shift方法一定会改变原数组。以上只展示数组中有关栈和队列的基本操作。因为本文重点在于介绍 JavaScript 的数组容器特性。更为硬核的数据结构和算法知识待更新。数组和字符串的爱恨情仇有时候刷题需要运用到数组和字符串互相转换的场景。相信大伙经常搞不清楚数组转字符串或者字符串转数组分别用到什么实例方法下文直接给出总结join 拼接 数组拼接字符串split 分割 分割字符串为数组数组转字符串join方法用于将所有数组成员拼接成字符串。用通俗易懂的语言理解就是将数组中的逗号,替换成指定分隔符并将数组转换成字符串。const arr [1, 2, 3, 4]; arr.join( | ); // 1 | 2 | 3 | 4逗号替换成分隔符( | ) // 不提供参数则默认用逗号拼接成字符串 arr.join(); // 1, 2, 3, 4若数组成员是undefined或null等空位自动转成空字符串。[undefined, null].join(#); // #undefined 空字符串, #null 空字符串以上三个字符串做拼接同样这是博主总结的join方法伪代码若觉得复杂可以直接跳过。/** * param {string} str * return {string} */ Array.prototype.join function (str ,) { let string ; for (let i 0; i this.size() - 1; i) { if (this.array[i] undefined || this.array[i] null) { // 若数组成员是 undefined 或 null 等空位则自动转换成空字符串 string str; } else { // 数组成员转换为字符串形式后拼接 string this.array[i].toString() str; } } if (this.array[this.size() - 1] undefined || this.array[this.size() - 1] null) { string ; } else { string this.array[this.size() - 1]; } return string; };字符串转数组split方法用于分割字符串。该方法就是join的逆向了。const str a|b|c; str.split(|); // [a, b, c] // 参数为空字符串则数组成员为字符串每一个字符 str.split(); // [a, |, b, |, c] // 省略参数则数组唯一成员是原字符串 str.split(); // [a|b|c]巧妙的双指针在刷题时经常会运用到双指针技巧。如颠倒排列数组元素。这个方法如果手撕相对比较简单如/** * return {element[]} */ Array.prototype.reverse function () { let i 0; let j this.size() - 1; for ( ; i j; i, j--) { let temp this.array[i]; this.array[i] this.array[j]; this.array[j] temp; } return this.array; };但有时如果想图省事想直接运用封装好的操作该如何做reverse方法提供了这个操作。const arr [a, b, c]; arr.reverse(); arr; // [c, b, a]如何自定义排序对于单调递增、单调递减场景的排序const nums [1, 3, 2, 5, 4]; nums.sort((a, b) a - b); nums; // [1, 2, 3, 4, 5]递增const nums [1, 3, 2, 5, 4]; nums.sort((a, b) b - a); nums; // [5, 4, 3, 2, 1]递减除了单调递增、单调递减场景的博主还整理了其余场景下的自定义排序根据绝对值递减排序nums [2,-3,-1,5,-4]; nums.sort((a, b) Math.abs(b) - Math.abs(a)); nums; // [ 5, -4, -3, 2, -1 ]对二维数组自定义排序nums [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]; nums.sort((a, b) { if (a[0] ! b[0]) { // 对 二维数组第一位 递减排序 return b[0] - a[0]; } else { // 在 二维数组第一位 相同的情况下对 二维数组第二位 递增排序 return a[1] - b[1]; } }); nums; // [[7, 0], [7, 1], [6, 1], [5, 0], [5, 2], [4, 4]]遍历数组独有实例方法在常规从头到尾遍历一个数组时大伙通常会用const nums [1, 2, 3, 4, 5]; for (let i 0; i nums.length; i ) { // 处理逻辑 }但是这么写写久了容易繁琐。特别是如果要完整遍历一次数组时。这时候可以用原生 JavaScript 中独有的遍历数组方法map、forEachmap 方法其中map方法传入的参数前两位分别是当前成员、当前位置这两个参数都可以根据实际情况省略如保留第一个、省略第二个或两个参数都省略但如果保留了第二个参数则第一个参数也要保留。const nums [1, 2, 3, 4, 5]; nums.map((elem) elem 1); // [2, 3, 4, 5, 6]返回新数组 nums; // [1, 2, 3, 4, 5]const nums [1, 2, 3, 4, 5]; nums.map((elem, index) elem * index); // [0, 2, 6, 12, 20]返回新数组 nums; // [1, 2, 3, 4, 5]elem 当前成员 nums[i]index 当前位置 iforEach 方法forEach方法和map方法类似唯一的区别在于仅仅在遍历元素的过程中操作数据不返回新数组。const nums [1, 2, 3, 4, 5]; nums.forEach((elem, index) elem * index); // 不返回新数组仅仅操作数据 nums; // [1, 2, 3, 4, 5]等价于const nums [1, 2, 3, 4, 5]; const result [...nums]; // 将原数组复制到 result 数组里 for (let i 0; i nums.length; i ) { result[i] nums[i] * i; // 操作原数组数据 } nums; // [1, 2, 3, 4, 5]一句话总结在遍历数组时操作数据的同时返回新数组map单纯用于操作数据 forEach但是要注意这种方法只适用于从头到尾的完整遍历。如果能意识到是完整遍历数组的场景则优先考虑map/forEach方法。结语本文是博主在刷题过程中针对 JavaScript 数组容器特性的系统性总结。从栈和队列的基本操作到数组与字符串的相互转换再到双指针、自定义排序和遍历方法逐一梳理了最常用的实例方法及其适用场景。不过再多的总结也需要不断的应用——知识终究要靠实践才能真正内化。如遇到「先进先出」场景时优先想到 push shift遇到「需要返回新数组」时优先使用 map 而非 forEach。只有反复练习才能将这些技巧变成肌肉记忆。实例方法总结实例方法改变原数组返回参数push✅ 改变新数组的长度pop✅ 改变被删除的最后一个元素unshift✅ 改变新数组的长度shift✅ 改变被删除的第一个元素join❌ 不改变拼接后的字符串split❌ 不改变字符串方法分割后的新数组reverse✅ 改变改变后的原数组sort✅ 改变排序后的原数组map❌ 不改变回调处理后的新数组forEach❌ 不改变undefined仅遍历操作参考文章Array 对象 - JavaScript 教程 - 网道