JavaScript Alert 函數執行順序問題

 

問題


前幾天使用 JavaScript 寫 HTML 頁面時遇到了一個奇怪的問題:css

我想實現的功能是經過 confirm() 彈窗讓用戶選擇不一樣的需求,每次選擇後都將選擇結果暫時輸出到頁面上,最後一次選擇結束後再一次性將選項傳到後端處理。 代碼相似於:html

var step1 = confirm("exec step1?");
    $('#result').html($('#result').html() + "\n" + step1);
    var step2 = confirm("exec step2?");
    $('#result').html($('#result').html() + "\n" + step2);
    var step3 = confirm("exec step3?");
    $('#result').html($('#result').html() + "\n" + step3);

    send(step1, step2, step3);

但是代碼運行後卻發現:每次在執行完 confirm 函數,用戶選擇選項以後,頁面並無刷新,step1, step2 的結果沒有實時刷新到頁面上,而是到最後一步跟 step3 一塊顯示了出來。前端

後續嘗試了 alert()prompt() 這兩個跟 confirm 相似的彈對話框函數,狀況都與此相同,它們都會跳過頁面渲染先被執行。web

此時,還有更詭異的狀況,咱們給某一個 div 裏賦值後,馬上 alert 此 div 裏的內容,會發現 alert 顯示正確的內容,而 div 裏的內容卻沒有更新,而且會一直阻塞到咱們點擊肯定。後端

如圖:瀏覽器

alert、prompt、confirm 三個函數都相似,接下來咱們就用最簡單的 alert 來講。網絡

文章歡迎轉載,請尊重做者勞動成果,帶上原文連接:http://www.cnblogs.com/zhenbianshu/p/8686681.html異步

分析


解決這個問題以前先了解一下它是怎麼致使的,而要了解它須要從 JavaScript 的線程模型提及。ide

JavaScript 引擎是單線程運行的,瀏覽器不管在何時都只且只有一個線程在運行 JavaScript 程序,初衷是爲了減小 DOM 等共享資源的衝突。但是單線程永遠會面臨着一個問題,那就是某一段代碼阻塞會致使後續全部的任務都延遲。又因爲 JavaScript 常常須要操做頁面 DOM 和發送 HTTP 請求,這些 I/O 操做耗時通常都比較長,一旦阻塞,就會給用戶很是差的使用體驗。函數

因而便有了事件循環(event loop)的產生,JavaScript 將一些異步操做或 有I/O 阻塞的操做全都放到一個事件隊列,先順序執行同步 CPU代碼,等到 JavaScript 引擎沒有同步代碼,CPU 空閒下來再讀取事件隊列的異步事件來依次執行。

這些事件包括:

  • setTimeout() 設置的異步延遲事件;
  • DOM 操做相關如佈局和繪製事件;
  • 網絡 I/O 如 AJAX 請求事件;
  • 用戶操做事件,如鼠標點擊、鍵盤敲擊。

解決


明白了原理, 再解決這個問題就有了方向,咱們來分析這個問題:

  1. 因爲頁面渲染是 DOM 操做,會被 JavaScript 引擎放入事件隊列;
  2. alert() 是 window 的內置函數,被認爲是同步 CPU代碼;
  3. JavaScript 引擎會優先執行同步代碼,alert 彈窗先出現;
  4. alert 有特殊的阻塞性質,JavaScript 引擎的執行被阻塞住;
  5. 點擊 alert 的「肯定」,JavaScript 沒有了阻塞,執行完同步代碼後,又讀取事件隊列裏的 DOM 操做,頁面渲染完成。

由上述緣由,致使了詭異的 「Alert執行順序問題」。 咱們沒法將頁面渲染變成同步操做,那麼只好把 alert() 變爲異步代碼,從而才能在頁面渲染以後執行。

對於這個解決方向,咱們有兩種方法可使用:

替換 Alert() 函數

首先咱們考慮替換掉 alert 函數的功能。其實大多數狀況下咱們替換掉 alert 並非它不符合咱們期待的執行順序,而是由於它實在是太醜了,並且也不支持各類美化,能夠想像在一個某一特定主題的網站上突然彈出來一個灰色單調的對話框是多麼不和諧。

這個咱們能夠考慮 Bootstrap 的 modal 模塊,Bootstrap 在絕大多數網站上都在應用,而多引入一個 modal 模塊也不會有多大影響。咱們使用 modal 構造一個彈出對話框的樣子,使用 modal 的 modal('toggle')/modal('show')/modal('hide') 方法能夠很方便地控制 modal 的顯隱。

替換掉對話框後,咱們還須要解決後續代碼執行的問題。使用 alert 函數時,咱們點擊肯定後代碼還會繼續執行,而使用咱們自定義的對話框可沒有這種功能了,須要考慮把後續代碼綁定在對話框的點擊按鈕上,這就須要使用 DOM 的 onclick 屬性了,咱們將後續函數內容抽出一個新的函數,在彈出對話框後將這個函數綁定在按鈕的 onclick 事件上便可。

這裏還須要注意,新函數內應該包括關閉 modal 對話框的內容。

固然,咱們還能夠再優化一下,抽象出來一個用來彈出對話框的函數替代 alert 函數,示例以下:

window.alert = function (message, callbackFunc) {
    $('#alertContent').html(message);
    $('#modal').show();
    $('#confirmButton').onclick(function () {
        $('#modal').hide();
        callbackFunc();
    });
};

如此,咱們在須要彈出框時調用新的 alert 函數,並傳入 callbackFunc ,在裏面作後續的事情就能夠了。

setTimeOut函數

固然,並非全部人都願意使用新的對話框替換 alert 函數的對話框,總感受上面的方法不是特別的優雅,對此,咱們能夠採用另外的方法解決這個問題。

前端的同窗應該對 setTimeout() 這個函數不陌生,使用它,能夠延遲執行某些代碼。而對於延遲執行的代碼,JavaScript 引擎老是把這些代碼放到事件隊列裏去,再去檢查是否已經到了執行時間,再適時執行。代碼進入事件隊列,就意味着代碼變成和頁面渲染事件同樣異步了。因爲事件隊列是有序的,咱們若是用 setTimeout 延時執行,就能夠實如今頁面渲染以後執行 alert 的功能了。

setTimeout 的函數原型爲 setTimeout(code, msec),code 是要變爲異步的代碼或函數,msec 是要延時的時間,單位爲毫秒。這裏咱們不須要它延時,只須要它變爲異步就好了,因此能夠將 msec 設置爲 0;

一樣,alert 以後的代碼咱們也須要處理,將它們跟 alert 一塊放到 setTimeout 裏異步執行。這樣,代碼就變爲 setTimeout("alert('msg');doSomething();", 0);,若是以爲代碼不夠美觀或字符串很差處理的話,能夠將後續代碼封裝成一個函數放到 doSomething() 裏便可。

小結


在上面的兩個解決方案中,都利用了 JavaScript 的回調函數,前者將函數所爲 alert 的參數並綁定到 DOM 的 onclick 事件,後者使用 setTimeout 將函數轉爲異步執行。JavaScript 的回調函數確實很是強大,使用起來也很簡單,可是卻有一個隱含的問題,就是回調嵌套問題,單層的回調很容易理解,但若是要實現像個人需求同樣,有多個 alert 和頁面渲染輪流執行的狀況,須要面臨的可能就是「回調地獄」, onclick 事件綁定裏的函數又要嵌套綁定 onclick 函數, setTimeout 裏還須要另外一個 setTimeout 語句,一旦出現問題,排查起來就比較麻煩了。

前端寫得很少,可能對 JavaScript 的理解會有些誤差,文章若有錯漏,還請在文章下面評論區指出。對於此問題,若是有大神有更好的解決方案,還請不吝賜教。

關於本文有什麼問題能夠在下面留言交流,若是您以爲本文對您有幫助,能夠點擊下面的 推薦 支持一下我,博客一直在更新,歡迎 關注

相關文章
相關標籤/搜索