天天看點

jquery的click無法觸發事件

  一個頁面需要在加載後勾選table中所有行的checkbox,于是就這樣寫

1 $("table thead tr th input[type='checkbox']").click();      

  結果一點反應也沒有,檢查好久,代碼沒有問題啊,好一番查詢,都要放棄了,終于找到原因:table還沒有渲染完

  咔嚓!我明明寫在$(function(){})中的代碼竟然在還沒有加載完時執行了!?

  原來JS是單線程,但是浏覽器是多線程,雖然說js是邊讀邊執行的,但是并不是真的邊讀邊執行的。浏覽器裡面至少有兩個線程,一個是渲染的,一個是js的,當渲染的線程工作的時候,js的線程是在休息中的,同樣,js線程工作的時候,渲染線程就在睡懶覺!

  無奈,最後隻能用延時觸發的方法了

1 setTimeout(function() {$("table thead tr th input[type='checkbox']").click();}, 1000);      

另附浏覽器線程知識:

jquery的click無法觸發事件

  假如我們要執行一些耗時的操作,比如加載一張很大的圖檔,我們可能需要一個進度條來讓使用者進行等待,在等待的過程中,整個js線程會被阻塞,後面的代碼不能正常運作,這可能大大的降低使用者體驗,這時候我們就期望擁有一個工作線程來處理這些耗時的操作。在傳統的html時代是基本不可能實作的,而現在,我們擁有一種叫做worker的東西。它是js裡的一個類,而我們隻需要建立它的執行個體就可以使用它。

1 var worker = new Worker(js file path);      

  構造函數的參數填上你的js檔案的路徑,這個js檔案将會在浏覽器新開的線程裡運作,而與原先的js引擎的線程并不影響。

  那麼既然互不影響,兩個線程之間要怎麼來聯系呢,答案其實已經在代碼裡了,那就是onPostMessage 和 onmessage這兩個函數,其中onPostMessage(data)的參數是你要傳遞的資料,而onmessage是一個回調函數,隻有在接受到資料時,onmessage會被回調,onmessage有一個隐藏的參數,那就是event,我們可以用event.data擷取到傳遞過來的資料來更新主線程。

  JavaScript的setTimeout與setInterval是兩個很容易欺騙别人感情的方法,因為我們開始常常以為調用了就會按既定的方式執行, 我想不少人都深有同感, 例如

1 setTimeout( function(){ alert(’你好!’); } , 0);  
2 setInterval( callbackFunction , 100);        

  認為setTimeout中的問候方法會立即被執行,因為這并不是憑空而說,而是JavaScript API文檔明确定義第二個參數意義為隔多少毫秒後,回調方法就會被執行. 這裡設成0毫秒,理所當然就立即被執行了.

  同理對setInterval的callbackFunction方法每間隔100毫秒就立即被執行深信不疑!

  但随着JavaScript應用開發經驗不斷的增加和豐富,有一天你發現了一段怪異的代碼而百思不得其解:

1 div.onclick = function(){  
2     setTimeout( function(){document.getElementById(’inputField’).focus();}, 0);  
3 };       

  既然是0毫秒後執行,那麼還用setTimeout幹什麼, 此刻, 堅定的信念已開始動搖.

  直到最後某一天 , 你不小心寫了一段糟糕的代碼:

1 setTimeout( function(){ while(true){} } , 100);  
2 setTimeout( function(){ alert(’你好!’); } , 200);  
3 setInterval( callbackFunction , 200);        

  第一行代碼進入了死循環,但不久你就會發現,第二,第三行并不是預料中的事情,alert問候未見出現,callbacKFunction也杳無音訊!

  這時你徹底迷惘了,這種情景是難以接受的,因為改變長久以來既定的認知去接受新思想的過程是痛苦的,但情事實擺在眼前,對JavaScript真理的探求并不會因為痛苦而停止,下面讓我們來展開JavaScript線程和定時器探索之旅!

拔開雲霧見月明

  出現上面所有誤區的最主要一個原因是:潛意識中認為,JavaScript引擎有多個線程在執行,JavaScript的定時器回調函數是異步執行的.

  而事實上的,JavaScript使用了障眼法,在多數時候騙過了我們的眼睛,這裡背光得澄清一個事實:

  JavaScript引擎是單線程運作的,浏覽器無論在什麼時候都隻且隻有一個線程在運作JavaScript程式.

  JavaScript引擎用單線程運作也是有意義的,單線程不必理會線程同步這些複雜的問題,問題得到簡化.

  那麼單線程的JavaScript引擎是怎麼配合浏覽器核心處理這些定時器和響應浏覽器事件的呢?下面結合浏覽器核心處理方式簡單說明.

  浏覽器核心實作允許多個線程異步執行,這些線程在核心制控下互相配合以保持同步.假如某一浏覽器核心的實作至少有三個常駐線程:javascript引擎線程,界面渲染線程,浏覽器事件觸發線程,除些以外,也有一些執行完就終止的線程,如Http請求線程,這些異步線程都會産生不同的異步事件。

  浏覽器中的JavaScript引擎是基于事件驅動的,這裡的事件可看作是浏覽器派給它的各種任務,這些任務可以源自JavaScript引擎目前執行的代碼塊,如調用setTimeout添加一個任務,也可來自浏覽器核心的其它線程,如界面元素滑鼠點選事件,定時觸發器時間到達通知,異步請求狀态變更通知等.從代碼角度看來任務實體就是各種回調函數,JavaScript引擎一直等待着任務隊列中任務的到來.由于單線程關系,這些任務得進行排隊,一個接着一個被引擎處理.

  不同的時間點,tn下面對應的小方塊代表該時間點的任務,假設現在是t1時刻,引擎運作在t1對應的任務方塊代碼内,在這個時間點内,我們來描述一下浏覽器核心其它線程的狀态.

GUI渲染線程:

  該線程負責渲染浏覽器界面HTML元素,當界面需要重繪(Repaint)或由于某種操作引發回流(reflow)時,該線程就會執行.本文雖然重點解釋JavaScript定時機制,但這時有必要說說渲染線程,因為該線程與JavaScript引擎線程是互斥的,這容易了解,因為JavaScript腳本是可操縱DOM元素,在修改這些元素屬性同時渲染界面,那麼渲染線程前後獲得的元素資料就可能不一緻了.

  在JavaScript引擎運作腳本期間,浏覽器渲染線程都是處于挂起狀态的,也就是說被”當機”了.

  是以,在腳本中執行對界面進行更新操作,如添加結點,删除結點或改變結點的外觀等更新并不會立即展現出來,這些操作将儲存在一個隊列中,待JavaScript引擎空閑時才有機會渲染出來.

GUI事件觸發線程:

  JavaScript腳本的執行不影響html元素事件的觸發,在t1時間段内,首先是使用者點選了一個滑鼠鍵,點選被浏覽器事件觸發線程捕捉後形成一個滑鼠點選事件,由圖可知,對于JavaScript引擎線程來說,這事件是由其它線程異步傳到任務隊列尾的,由于引擎正在處理t1時的任務,這個滑鼠點選事件正在等待處理.

定時觸發線程:

  注意這裡的浏覽器模型定時計數器并不是由JavaScript引擎計數的,因為JavaScript引擎是單線程的,如果處于阻塞線程狀态就計不了時,它必須依賴外部來計時并觸發定時,是以隊列中的定時事件也是異步事件.

  由圖可知,在這t1的時間段内,繼滑鼠點選事件觸發後,先前已設定的setTimeout定時也到達了,此刻對JavaScript引擎來說,定時觸發線程産生了一個異步定時事件并放到任務隊列中, 該事件被排到點選事件回調之後,等待處理.

  同理, 還是在t1時間段内,接下來某個setInterval定時器也被添加了,由于是間隔定時,在t1段内連續被觸發了兩次,這兩個事件被排到隊尾等待處理.

  可見,假如時間段t1非常長,遠大于setInterval的定時間隔,那麼定時觸發線程就會源源不斷的産生異步定時事件并放到任務隊列尾而不管它們是否已被處理,但一旦t1和最先的定時事件前面的任務已處理完,這些排列中的定時事件就依次不間斷的被執行,這是因為,對于JavaScript引擎來說,在處理隊列中的各任務處理方式都是一樣的,隻是處理的次序不同而已.

  t1過後,也就是說目前處理的任務已傳回,JavaScript引擎會檢查任務隊列,發現目前隊列非空,就取出t2下面對應的任務執行,其它時間依此類推,由此看來:

  如果隊列非空,引擎就從隊列頭取出一個任務,直到該任務處理完,即傳回後引擎接着運作下一個任務,在任務沒傳回前隊列中的其它任務是沒法被執行的.

  相信您現在已經很清楚JavaScript是否可多線程,也了解了解JavaScript定時器運作機制了,下面我們來對一些案例進行分析:

案例1:setTimeout與setInterval

1 setTimeout(function(){  
2    /* 代碼塊... */  
3    setTimeout(arguments.callee, 10);  
4 }, 10);  
5   
6 setInterval(function(){  
7    /*代碼塊... */  
8  }, 10);        

繼續閱讀