2012-07-04

好用卻有點難懂的 jQuery live function

先講好用的部份,live 可以為尚未存在的 Dom 物件註冊 Event Listener,先不管怎麼辦到的,Ajax 是現在網頁的常客,當 Ajax 修改頁面時,還要為新增加的 Dom 物件註冊 Event Listener 就真的有點累,尤其是這些 Event Listener 已經註冊過了,還要小心不要替舊有的 Dom 物件又註冊一次,這時 live 就很好用了。
$('div.d').live('click', function(e){

    log(e, 'from live');

});
只要這麼做,不管已經出生的還是未出生的 div.d,都可以得到這個 click 註冊,實務上,在實做樹狀結構時,因為目錄開開關關的,檔案來來去去的,要逐一註冊 Listener 真的會瘋掉,用 live 就可以「一招斃命」了。

再來是難懂的地方,還好,一般不太會遇到。

先說說 live 大概是怎麼實做的,對於難懂的地方會比較有概念。

對 live 來說,context 很重要,哪個 context?就是 jQuery selector 的 context,一般在寫 selector 時並不會加 context,所以 context 有預設值,就是 document。
$('div.d');

// 等於 

$('div.d', document)

// 也可以自訂 context

$('div.d', '#a');
最後一個 selector 表示 #a 以下的所有 div.d,相對於前一個 selector 是指 document 以下的所有 div.d,也就所有的 div.id 了。

本來以為 jQuery 是將 live 的 handler 暫存起來,等到有新的 Dom 物件產生時再判斷是否加上去,後來發現不是這樣,jQuery 是在 live 的 context(或者 document,還不確定) 註冊一個該 Event type 的 Listener,但內容不完全等於 live 的 handler,而是包裝過,只有符合 live selector 的 Dom 物件會執行該 Listener,那誰來觸發 context 這個 Listener 呢?泡泡作用,舉例說明比較好懂。


Dom 起始狀態如上,在頁面載入執行 live 與其他註冊。
(function(){

    $('body').click(function(e){

        log(e, 'from body');

    });

    $('div.#a').click(function(e){

        log(e, 'from a');

    });

    $('div.#b').click(function(e){

        log(e, 'from b');

    });

    $('div', '#a').live('click', function(e){

        log(e, 'from live');

    });

});
先分別對 body、a 與 b 註冊 click listener,用來追蹤泡泡過程,最後再為 a 裡所有的 div 註冊一個 click 的 live listener。

註冊完成後再插入兩個 Div c 與 d 如下。


也就是說,除了 b 有來自 live 的 click listener,c 與 d 也會有,另外 body、a 與 b 有一般的 click listener。

先點 body,來自 body 的一般 click event,沒問題。
from body - click(T)BODY.(C)BODY.
再點 a,來自 a 並泡泡給 body 的一般 click event,也沒問題。
from a - click(T)DIV.a(C)DIV.a
from body - click(T)DIV.a(C)BODY.
再點 b,分成兩部份,第一部份是來自 b 並泡泡給 a 與 body,這部份沒問題,第二部份是來自於 b 的 live event,有沒發現奇怪的地方?為什麼 live 的 b 沒有傳給 a 或 body,live event 沒有「泡泡作用」?還有為什麼 live b 是在 body 之後執行?
from b - click(T)DIV.b(C)DIV.b
from a - click(T)DIV.b(C)DIV.a
from body - click(T)DIV.b(C)BODY.
from live - click(T)DIV.b(C)DIV.b
再點 c,前三個是一般的泡泡,後兩個是 live event,live 的 c 有傳給 live 的 b,live event 有「泡泡作用」?
from b - click(T)DIV.c(C)DIV.b
from a - click(T)DIV.c(C)DIV.a
from body - click(T)DIV.c(C)BODY.
from live - click(T)DIV.c(C)DIV.c
from live - click(T)DIV.c(C)DIV.b
最後點 d,前三個是一般的泡泡,後三個是 live event,live 的 d 有傳給 live 的 c 與 b,live event 有「泡泡作用」?
from b - click(T)DIV.d(C)DIV.b
from a - click(T)DIV.d(C)DIV.a
from body - click(T)DIV.d(C)BODY.
from live - click(T)DIV.d(C)DIV.d
from live - click(T)DIV.d(C)DIV.c
from live - click(T)DIV.d(C)DIV.b
正確來說,live event 的泡泡只會在 live 裡傳遞,不會傳到一般 listener 裡,原因就是剛提到的 context。

事實上從上面的執行順序也可以看出端倪,為什麼 live event 總是在 body 之後才出現?因為 jQuery 是在 body click listener 之後才執行 live handler一般 click listener 和 live listener 是獨立的兩條線,第一條線先走完才會走第二條線也就是說 live listener 完全是 jQuery 自己手工造出來的,不是 browser 或者 Javascript 觸發的,證明方式就是在 live context 與 body 之間的 click 路徑上,在任一個 Dom 的 click listener 呼叫 e.stopPropagation() 或者 return false,就可以完全制止 live 的執行。

live 重點整理如下:
  • 一般 click listener 執行完成後才會執行 live click listener。
  • 在一般的 click listener 呼叫 e.stopPropagation() 或者 return false,live click listener 就不會執行,也就說 click event 傳不到 body,live 就廢掉。
  • live 裡的泡泡完全是 jQuery 用 live selector 去造出來的。

最後再玩玩,若在 live handler 裡呼叫 e.stopPropagation() 或者 return false,也可以制止 live泡泡的傳遞。

live 也像 bind 一樣可以 unbind,不過 live 是用 die。
---

沒有留言:

張貼留言