由一道'惡搞'排序引起的思考

做者:隨便@毛豆前端javascript

背景

​ 偶然在技術羣裏看到一個惡搞的排序。第一眼看着以爲這個排序算法是惡搞的,可是惡搞的排序算法卻給出了正確的排序結果。不由讓人想去進一步瞭解它的運行原理。前端

Event Loop介紹

​ 咱們知道JavaScript是單線程的,程序運行時,只有一個線程存在。單線程的JavaScript一段一段的執行,前面的執行完了,再執行後面的。可是若是遇到一個耗時好久的任務,好比接口請求、I/O操做,此時後面的任務若是一直等待,不只浪費資源,而且頁面會卡住,交互也不好。爲了解決這個問題,JavaScript將任務分爲同步和異步任務,來進行不一樣的處理。java

​ JavaScript在執行時,會將同步任務在執行棧(execution context stack)中,按照順序在主線程上執行,前面的任務執行完了,再執行後面的。遇到異步任務不會停下來等待,而是將其掛起,繼續執行當前棧中的同步任務。當異步任務有返回結果時,將異步任務執行完成後的結果加入任務隊列(task queue),一般任務隊列中存放的都是異步完成後的回調函數。git

​ 當執行棧中的任務完成後,空閒的主線程就會讀取任務隊列中是否有任務。若是有,主線程就會把最早進入任務隊列的任務加入到執行棧中,執行棧中的任務執行完成後,主線程便又會去查詢任務隊列中的任務,並讀取到執行棧中去執行。這個過程是循環往復的,這即是Event Loop,事件循環。github

​ 網上有一張流傳很廣的圖對這一過程進行了總結:ajax

​ 由圖可知,JavaScript在運行時產生堆和棧,ajax、setTimeout等異步任務被掛起,異步任務返回結果加入到任務隊列,主線程會循環往復的讀取任務隊列中的任務,並加入執行棧中執行。

"排序"代碼分析

​ OK,說了那麼多,咱們終於看到跟這個排序算法有關係的關鍵詞了:setTimeout。做爲一個異步任務,在循環數組的過程當中,每次根據當前值,延時往任務隊列添加回調函數,填充數據到新的數組中。因爲值有大小的分別,小的值延時時間短,就會被先添加到任務隊列中,最終利用Event Loop的時間差,達到了排序的目的。算法

​ 不考慮延時的問題,單純從代碼來看,完成整個排序只是用了一遍循環,這個排序代碼的時間複雜度實際上是O(n)的。而且從目前的例子來看:排序過程當中,相同大小的數據在排序先後不會改變順序,是穩定的排序算法。咱們甚至能夠把這段代碼封裝修改一下,經過一個回調來獲得排序後的數組:數組

function eventLoopSort(list, callback) {
  var newList = [];
  var sum = 0;
  list.forEach(item => {
    setTimeout(() => {
      newList.push(item);
    }, item * 100)
    sum += item;
  });
  setTimeout(() => {
    callback && callback(newList);
  }, sum * 100);
}

var list = [1, 1, 4, 6, 2, 3, 9, 8, 7];
eventLoopSort(list, data => {
  console.log(data) // [1, 1, 2, 3, 4, 6, 7, 8, 9]
})
複製代碼

問題總結

​ 一本正經的講了這麼多,仍是改變不了這個排序是惡搞,不能夠應用到實際代碼中的現實。由於在面對大量數據時,除了setTimeout延時較長以外,這個排序仍是會出錯的。考慮到代碼自己執行也是須要耗時的,在面對大量數據時,前面數據執行時間較長,長到能夠抵消延時的時間差的時候,排序就會徹底出錯了。瀏覽器

​ 最後,第一次寫出這段"玩笑"代碼的人,必定對瀏覽器的運行機制很是的瞭解。這也是我想經過這篇文章表達的意思,但願能經過這個惡搞,有趣的形式,對你的學習有所幫助。異步

相關文章
相關標籤/搜索