由於JavaScript單線程的特性,在執行CPU密集型任務時(如大數組排序)會阻塞網頁的渲染,解決辦法一般是使用setTimeout函數對任務進行分割,或者把這類任務交由 web worker 處理。今天遇到一個對大數組排序的問題,需要使用任務分割來避免阻塞,在這裏記錄下我的實現方法。在實現時我將 while 語句進行了分割,避免出現大循環體語句一直進行計算,主要代碼如下:
// 將 while 控制語句進行分割
// expression 相當於 while 的條件判斷
// statement 相當於 while 循環體語句
async function while_async(expression, statement){
while(expression()){
await while_seg(expression, statement);
}
}
let while_seg = (expression, statement) => new Promise((resolve, reject) => {
setTimeout(()=>{
let i = 0;
let while_size = 50000;
while(expression()){
i++;
statement();
// 如果 while 內循環次數大於 while_size ,則進入事件循環
if(i>while_size)break;
}
resolve();
}, 0);
});
應用在大數組排序上,效果對比可以在這裏查看:https://codepen.io/liuyaqi/pen/dmZdPe
height="351" width="650" scrolling="no" title="while_seg" src="//codepen.io/liuyaqi/embed/dmZdPe/?height=351&theme-id=0&default-tab=result&embed-version=2" allowfullscreen="true">See the Pen <a href="https://codepen.io/liuyaqi/pen/dmZdPe/">while_seg</a> by liuyaqi (<a href="https://codepen.io/liuyaqi">@liuyaqi</a>) on <a href="https://codepen.io">CodePen</a>. 全部代碼如下:
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
div{
width: 100px;
height: 100px;
margin-left: 0;
background-color: aqua;
animation: animation 3s infinite ;
}
div:hover{
background-color: white;
}
@keyframes animation {
0% {
margin-left: 0;
}
50% {
margin-left: 500px;
}
100% {
margin-left: 0;
}
}
</style>
</head>
<body>
<div></div>
<button id="sort">sort</button>
<button id="s_sort">setTimeout+sort</button>
</body>
<script>
let data = [];
let size = 2000000;
for(let i=0; i<size; i++){
data.push(Math.floor(Math.random()*size));
}
console.log(data);
document.querySelector("#sort").onclick = ()=>{
data.sort((a, b) => a-b);
console.log("排序結果", data);
alert("done");
}
document.querySelector("#s_sort").onclick = async ()=>{
let res = await mergeSort_async(data);
console.log("排序結果", res);
alert("done");
}
// 歸併排序算法
async function mergeSort_async(array){
var length = array.length;
// 如果數組長度小於10000,直接排序
if(length<10000){
return array.sort((a, b) => a-b);
}
var mid = Math.floor(length / 2),
left = array.slice(0, mid),
right = array.slice(mid, length);
return await merge_async(await mergeSort_async(left), await mergeSort_async(right));
}
async function merge_async(left, right){
var result = [],
il = 0,
ir = 0;
var args = {il, ir, result, left, right};
await while_async(()=>(il < left.length && ir < right.length), ()=>{
if(left[il] < right[ir]){
result.push(left[il++]);
}else{
result.push(right[ir++]);
}
});
await while_async(()=>(il < left.length), ()=>{
result.push(left[il++]);
});
await while_async(()=>(ir < right.length), ()=>{
result.push(right[ir++]);
});
return result;
}
// 將 while 控制語句進行分割
// expression 相當於 while 的條件判斷
// statement 相當於 while 循環體語句
async function while_async(expression, statement){
while(expression()){
await while_seg(expression, statement);
}
}
let while_seg = (expression, statement) => new Promise((resolve, reject) => {
setTimeout(()=>{
let i = 0;
let while_size = 50000;
while(expression()){
i++;
statement();
// 如果 while 內循環次數大於 while_size ,則進入事件循環
if(i>while_size)break;
}
resolve();
}, 0);
});
</script>
</html>