/ Work

[jQuery note.] 關於 bind, live 與 evnets 的筆記

UPDATE, 簡易的 plugin,為了強姦干擾 Events 執行順序而做的。

噗浪討論串:http://www.plurk.com/p/7u9br0

    (function($) {
        $.fn.superbind = function(order, type, data, fn) {
            if ( $.isFunction( data ) ) {
                fn = data;
                data = undefined;
            }

            var order = (typeof order !== "number" || order < 0) ? 0 : order;
            return this.each(function() {
                /* TODO: fire event before inline event.
                if ( order === 0 && typeof $(this).attr("on"+type) === "function") {
                }
                */
                var _ = setTimeout( function() {
                    var guid = 0;
                    $.each( $.cache, function() {
                        if ( this.events && this.events[ "live" ] && this.events[ type ] ) {
                            var len = this.events[ type ].length - 1;
                            guid = parseInt(this.events[ "live" ][ len ].guid) + 1;
                        }
                    });
                    $.each( $.cache, function() {
                        var reorder = [], events_len = 0, reorder_len = 0;
                        var obj = {
                            data: data,
                            guid: guid,
                            handler: fn,
                            namespace: "",
                            type: type
                        };
                        if ( this.events && this.events[ type ] && !this.events[ "live" ] ) {
                            events_len = this.events[ type ].length;
                            order = (order > events_len) ? events_len : order;

                            for(var i in this.events[ type ]) {
                                if (order === events_len && parseInt(i) === events_len-1) {
                                    reorder.push(this.events[ type ][i]);
                                    reorder.push(obj);
                                    order = -1;
                                } else if ( parseInt(i) === order || order === 0) {
                                    reorder.push(obj);
                                    reorder.push(this.events[ type ][i]);
                                    order = -1;
                                } else {
                                    reorder.push(this.events[ type ][i]);
                                }
                            }
                            this.events[ type ] = reorder;
                            reorder = [];
                            delete reorder;
                        }
                    });
                }, 20);

                return this;
            });
        };
    })(jQuery);

噗浪討論串:http://www.plurk.com/p/7u4b0j

最近為了一些應用上的效果,所以特別研究了一下 Events 的資料。挖開 jQuery 之後才發現,這是一個很奇妙的世界(疑)。結合各家神人討論,所以我來心得報告一下(喂)。特別感謝 大澤木小鐵, 費拉諾蘭大公 鼎力相助(一拜)。

通常在 jQuery 裡面,我們使用 Events 的作法,有三種:

    // 1. 使用 bind 來綁定 event
    $("#elem").bind("click", function(e) { ... });
    // 2. 直接使用 event
    $("#elem").click(function(e) { ... });
    // 3. 使用 live 來綁定 event
    $("#elem").live("click", function(e) { ... });

    <!-- 4. inline 的寫法 -->
    <div id="elem" onclick=" ... "> ... </div>

4 > 2 = 3 > 1

疑?不要問我為什麼,因為實驗的結果就是這樣(喂)。當我們把 jQuery 三種使用 Events 的資料傾印出來的時候,會發現一件很神奇的事情,使用 bind/click 直接綁定的 Events,其資料是儲存於元件本身,而使用 live 綁定時,資料會儲存於 document 裡面。再根據費拉諾蘭大公所述,三者的資料都會儲存於 $.cache 裡面(使用陣列儲存)。

所以,我們分別來看這些事情:

<!doctype html>
<html lang="en" class="no-js">
<head>
    <meta charset="utf-8">
    <style>
    #main {
      width: 400px;
      height: 600px;
      border: 1px solid #000000;
      color: #ff3333;
    }
  </style>
</head>
<body>
    <div id="container">
        <div id="main" onclick="console.log(4);"></div>
    </div>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
    <script>!window.jQuery && document.write('<script src="./js/jquery-1.4.2.min.js"><\/script>')</script>
    <script>
    $(document).ready(function() {
        $("#main").bind("click", function(e) {
            console.log(3);
            console.log($.data(this).events);
        });
        $("#main").click(function(e) {
            console.log(2);
            console.log($.data(this).events);
        });
        $("#main").live("click", function(e) {
            console.log(1);
            console.log($.data(this).events);
            console.log($.data(document).events);
            console.log("$.cache");
            console.log($.cache);
        });
    });
    </script>
</body>
</html>

依照 $.cache 的儲存方法,他會將元素上的 click/bind 跟 live 分開存放,但是請注意,他並不會儲存 DOM 裡 inline 寫法的 onclick,所以,並不能單純使用這個方法去判斷是否有該 Events 存在或是執行。此外,因為 $.cache 是以陣列的方式儲存這些事情,而且他也並不能直覺的取得所綁定的對象,所以在資訊取出上,有相對麻煩的地方。

當你將 $.cache 列出來時,你會發現 live 這個方法,會產生一個相同的 event 方法,換句話說,你使用 bind/click 去綁定一個 Event 時,他只會有一個 event 方法,而,倘若你使用 live 去綁定一個 Event 時,他同時會衍生出一個該 Event 的 event 方法(超饒舌)。

// 這一段 Javascript 可以加在上述 live 的 function 底下
console.log("======================");
for(i in $.cache) {
    console.log("這是第 "+i.toString()+" 個 Event cache。");
    var events = $.cache[i].events || null;
    if(typeof events === "object") {
        for(event in events) {
            console.log("這是使用 "+event.toString()+" 綁定。");
            console.log(events[event]);
            if(events[event].length > 1) {
                for(j in events[event]) {
                    console.log("這是第 "+(parseInt(j)+1).toString()+" 個 Event 綁定。");
                    console.log(events[event][j]);
                }
            }
        }
    }
}

這裡有一個不小心實驗出來的問題,當你使用 bind/click 時,倘若在 handler function 中使用了 return false; 的話,那麼 live 所綁定的 Event 會略過不執行。原因是,因為 live 把 Event 偷偷地綁在 document 上面,由於 DOM 中 addEventListener 的規則,你使用 return false 就等同於 stopPropagation() 的意思(其實 return false 也會連帶執行 preventDefault())。

所以,在 document 的子元件執行 stopPropagation(),想當然爾,document 所綁定的 Events 就不會被執行了,這一點可得小心為上。

其實我只是想做一個可以惡搞 Events 執行順序的 plugin 而已(攤手)。