道格拉斯·克罗克福特在《JavaScript权威指南》一书中指出,“shift通常比pop慢得多”。jsPerf证实了这一点。是否有人知道为什么会出现这种情况?从一个不太复杂的角度来看,它们似乎正在做着类似的事情。
道格拉斯·克罗克福特在《JavaScript权威指南》一书中指出,“shift通常比pop慢得多”。jsPerf证实了这一点。是否有人知道为什么会出现这种情况?从一个不太复杂的角度来看,它们似乎正在做着类似的事情。
为了移除返回的项而无需重新寻址数组并使其失效,shift()
需要移动整个数组;而pop()
则可以通过减少其长度来简单地删除最后一项。
shift
函数可以简单地增加它。尽管如此,它“可能”做到这一点,这将消除这种减速。 - geekosaurshift()
需要重新索引整个数组,而pop()
则不需要。
pop()
只是简单地移除了数组中的最后一个元素。因此,元素并没有发生移动;只需要更新.length
即可。
shift()
会移除数组中的第一个元素。这需要对数组中所有元素进行重新索引,使得[1]
变成[0]
等等。
我在使用node (使用chrome v8引擎) 进行一些测试时发现,对于长度不超过120k的数组而言,shift方法和pop方法的性能相差不大。但是当数组长度超过120K时,shift方法的性能急剧下降。
var sum;
var tests = [125000,130000];
console.log(JSON.stringify(process.versions));
tests.forEach(function(count) {
console.log('Testing arrays of size ' + count);
var s1 = Date.now();
var sArray = new Array(count);
var pArray = new Array(count);
for (var i = 0; i < count ; i++) {
var num = Math.floor(Math.random() * 6) + 1
sArray[i] = num;
pArray[i] = num;
}
console.log(' -> ' + (Date.now() - s1) + 'ms: built arrays with ' + count + ' random elements');
s1 = Date.now();
sum = 0;
while (pArray.length) {
sum += pArray.pop();
}
console.log(' -> ' + (Date.now() - s1) + 'ms: sum with pop() ' + count + ' elements, sum = ' + sum);
s1 = Date.now();
sum = 0;
while (sArray.length) {
sum += sArray.shift();
}
console.log(' -> ' + (Date.now() - s1) + 'ms: sum with shift() ' + count + ' elements, sum = ' + sum);
});
输出:
{"http_parser":"1.0","node":"0.10.22","v8":"3.14.5.9","ares":"1.9.0-DEV","uv":"0.10.19","zlib":"1.2.3","modules":"11","openssl":"1.0.1e"}
Testing arrays of size 125000
-> 14ms: built arrays with 125000 random elements
-> 2ms: sum with pop() 125000 elements, sum = 436673
-> 6ms: sum with shift() 125000 elements, sum = 436673
Testing arrays of size 130000
-> 50ms: built arrays with 130000 random elements
-> 1ms: sum with pop() 130000 elements, sum = 455971
-> 54372ms: sum with shift() 130000 elements, sum = 455971
这两者的差异可能微不足道——未经优化的执行器运行shift
的速度可能比pop
慢得多,但经过优化的执行器则不会。
您可以像这样进行优化:
let WrapArray = _=>{
//Ensure no other ref to `_`.
let numlike = _=>isNaN(_)?false:true
let num = _=>Number(_)
{
let shift_q = 0
return new Proxy(_, {
get(first_t, k){
switch(k){
case 'shift': return (z={})=>(z.r=first_t[0 + shift_q], delete first_t[0 + shift_q++], z.r)
break; case 'length': return first_t.length - shift_q
break; default: return first_t[numlike(k)?num(k) +/*todo overflowguard*/shift_q:k]
}
},
set(first_t, k, v){
switch(k){
case 'length': first_t.length = v + shift_q
break; default: first_t[numlike(k)?num(k) +/*todo overflowguard*/shift_q:k] = v
}
},
has(first_t, k){
return (numlike(k)?num(k) +/*todo overflowguard*/shift_q:k) in first_t
},
deleteProperty(first_t, k){
delete first_t[numlike(k)?num(k) +/*todo overflowguard*/shift_q:k];return 543
},
apply(first_t, t, s){
first_t.call(t, s)
},
construct(first_t, s, t){
new first_t(...s)
},
})
}
}
(_=WrapArray(['a','b','c'])).shift()
console.log(_.length/*2*/, _[0]/*b*/, _[1]/*c*/, _[2]/*undefined*/)
由于 shift() 会重新索引数组,所以在大数组上使用 shift 方法会非常缓慢。
var array = [];
for(var i = 0;i< 1000000;i++){
array.push(i)
}
var start = new Date().getTime()
for(var i = 0; i< 100000; i++){
array.shift();
}
var duration = new Date().getTime() - start;// duration is so large, greater than 3 minutes
But the duration is just 8ms when using linked-queue
var LQueue = require('linked-queue')
var queue = new LQueue()
for(var i = 0;i< 1000000;i++){
queue.enqueue(i);
}
console.log("Queue length:"+ queue.length);
var start = new Date().getTime()
queue.dequeueAll(function(data){
})
var end = new Date().getTime();
console.log("Time:" + (end - start));// 8 ms
console.log("Queue length:"+ queue.length);
pop()
或shift()
的结果,因此现代浏览器会将其优化为无操作。这里是一个修改后的版本,显示了pop()
确实比shift()
更快:http://jsperf.com/shift-vs-pop-and-reverse-plus-pop/7 - Nate