pop/push, shift/unshift 方法
队列(queue)是最常见的使用数组的方法之一。在计算机科学中,这表示支持两个操作的一个有序元素的集合:
push
在末端添加一个元素.shift
取出队列首端的一个元素,整个队列往前移,这样原先排第二的元素现在排在了第一。
这两种操作数组都支持。
队列的应用在实践中经常会碰到。例如需要在屏幕上显示消息队列。
数组还有另一个用例,就是数据结构 栈。
它支持两种操作:
push
在末端添加一个元素.pop
从末端取出一个元素.
所以新元素的添加和取出都是从“末端”开始的。
栈通常被被形容成一叠卡片:要么在最上面添加卡片,要么从最上面拿走卡片:
对于栈来说,最后放进去的内容是最先接收的,也叫做 LIFO(Last-In-First-Out),即后进先出法则。而与队列相对应的叫做 FIFO(First-In-First-Out),即先进先出。
JavaScript 中的数组既可以用作队列,也可以用作栈。它们允许你从首端/末端来添加/删除元素。
这在计算机科学中,允许这样的操作的数据结构被称为 双端队列(deque)。
作用于数组末端的方法:
pop
取出并返回数组的最后一个元素:
let fruits = ["Apple", "Orange", "Pear"];
alert( fruits.pop() ); // 移除 "Pear" 然后 alert 显示出来
alert( fruits ); // Apple, Orange
fruits.pop()
和 fruits.at(-1)
都返回数组的最后一个元素,但 fruits.pop()
同时也删除了数组的最后一个元素,进而修改了原数组。
-
push
在数组末端添加元素:
let fruits = ["Apple", "Orange"];
fruits.push("Pear");
alert( fruits ); // Apple, Orange, Pear
调用 fruits.push(...)
与 fruits[fruits.length] = ...
是一样的。
作用于数组首端的方法:
shift
取出数组的第一个元素并返回它:
let fruits = ["Apple", "Orange", "Pear"];
alert( fruits.shift() ); // 移除 Apple 然后 alert 显示出来
alert( fruits ); // Orange, Pear
unshift
在数组的首端添加元素:
let fruits = ["Orange", "Pear"];
fruits.unshift('Apple');
alert( fruits ); // Apple, Orange, Pear
push
和 unshift
方法都可以一次添加多个元素:
let fruits = ["Apple"];fruits.push("Orange", "Peach");
fruits.unshift("Pineapple", "Lemon");// ["Pineapple", "Lemon", "Apple", "Orange", "Peach"]
alert( fruits );
性能
push/pop
方法运行的比较快,而 shift/unshift
比较慢。
为什么作用于数组的末端会比首端快呢?让我们看看在执行期间都发生了什么:
fruits.shift(); // 从首端取出一个元素
只获取并移除索引 0
对应的元素是不够的。其它元素也需要被重新编号。
shift
操作必须做三件事:
- 移除索引为
0
的元素。 - 把所有的元素向左移动,把索引
1
改成0
,2
改成1
以此类推,对其重新编号。 - 更新
length
属性。
数组里的元素越多,移动它们就要花越多的时间,也就意味着越多的内存操作。
unshift
也是一样:为了在数组的首端添加元素,我们首先需要将现有的元素向右移动,增加它们的索引值。
那 push/pop
是什么样的呢?它们不需要移动任何东西。如果从末端移除一个元素,pop
方法只需要清理索引值并缩短 length
就可以了。
pop
操作的行为:
fruits.pop(); // 从末端取走一个元素
pop
方法不需要移动任何东西,因为其它元素都保留了各自的索引。这就是为什么 pop
会特别快。
push
方法也是一样的。