1. 程式人生 > >一步一步DIY zepto庫,研究zepto原始碼3 -- event模組

一步一步DIY zepto庫,研究zepto原始碼3 -- event模組

上面的博文介紹的都是原始碼src下的zepto.js檔案,接著我們來看看zepto的事件模組,對應檔案是event.js

1.繫結事件

例項Demo

    <div id="foo1">foo1</div>
    <div id="foo2">foo2</div>
    <div id="foo3">foo3
        <div id="foo31">foo31</div>
    </div>
    <a href="demo1.html" class="my-a">
demo1.html</a> <script src="zepto.js"></script> <script type="text/javascript"> var div1 = $('#foo1'); var div2 = $('#foo2'); $('body').on('click', '#foo1', function(event) { console.log(event); event.preventDefault(); alert("點選"); }); $('body'
).on('click', '#foo2', 'test',function(event) { event.preventDefault(); alert("點選foo2"+event.data);//點選foo2test }); $('#foo3').on('click', function(event) { alert("點選"); }); $('body').on('click', '.my-a', false);//不跳轉
</script>

1.1 handlers物件

handlers

物件的資料格式如下:

{
  1: [ // handlers的值為DOM元素的_zid
    {
      del: function() {}, // 實現事件代理的函式
      e: "click", // 事件名稱
      fn: function() {}, // 使用者傳入的回撥函式
      i: 0, // 該物件在數組裡的下標
      ns: "", // 事件的名稱空間,只用使用$.fn.triggerHandler時可用,$.fn.trigger不能使用。
      proxy: function(e) {}, // 真正繫結事件時的回撥函式,裡面判斷呼叫del或者fn
      sel: undefined // 要進行事件代理時傳入的selector
    }
  ]
}

1.2 全域性變數

var _zid = 1, //用來生成標示元素和回撥函式的id,每標示一個就+1
    undefined,
    handlers = {},
    slice = Array.prototype.slice,
    isFunction = $.isFunction,
    isString = function(obj) {
        return typeof obj == 'string';
    },
    specialEvents = {},
    focusinSupported = 'onfocusin' in window,
    focus = { focus: 'focusin', blur: 'focusout' },
    hover = { mouseenter: 'mouseover', mouseleave: 'mouseout' };

1.3 新增三個方法:isDefaultPrevented、isDefaultPrevented和isPropagationStopped

  • 如果preventDefault()被該事件的例項呼叫,那麼返回true。 這可作為跨平臺的替代原生的defaultPrevented屬性,如果defaultPrevented缺失或在某些瀏覽器下不可靠的時候
  • 如果stopImmediatePropagation()被該事件的例項呼叫,那麼返回true。Zepto在不支援該原生方法的瀏覽器中實現它(例如老版本的Android)
  • 如果stopPropagation()被該事件的例項呼叫,那麼返回true

通過改寫原生的preventDefault、stopImmediatePropagation和stopPropagation方法實現新增三個方法

新增的三個方法:

var returnTrue = function() {
        return true;
    },
    returnFalse = function() {
        return false;
    }, // 構建事件物件時所不要的幾個屬性:returnValue、layerX和layerY(還有以大寫字母開頭的屬性?)
    ignoreProperties = /^([A-Z]|returnValue$|layer[XY]$)/,
    // 事件物件需要新增的三個方法名
    eventMethods = {
        preventDefault: 'isDefaultPrevented',
        stopImmediatePropagation: 'isImmediatePropagationStopped',
        stopPropagation: 'isPropagationStopped'
    };
// 新增eventMethods裡面的三個方法:isDefaultPrevented、isDefaultPrevented和isPropagationStopped
function compatible(event, source) {
    if (source || !event.isDefaultPrevented) {
        source || (source = event);
        //遍歷eventMethods物件,name是key,predicate是value
        $.each(eventMethods, function(name, predicate) {
            var sourceMethod = source[name];
            event[name] = function() {
                this[predicate] = returnTrue;
                return sourceMethod && sourceMethod.apply(source, arguments);
            }
            event[predicate] = returnFalse;
        })
        try {
            event.timeStamp || (event.timeStamp = Date.now())
        } catch (ignored) {}
        // 設定isDefaultPrevented預設指向的函式
        // 如果有defaultPrevented屬性,就根據defaultPrevented的值來判斷
        if (source.defaultPrevented !== undefined ? source.defaultPrevented :
            'returnValue' in source ? source.returnValue === false :
            //getPreventDefault和defaultPrevented屬性類似,不過是非標準的。為了相容沒有defaultPrevented引數的瀏覽器
            source.getPreventDefault && source.getPreventDefault())
            event.isDefaultPrevented = returnTrue;
    }
    return event;
}

1.4 $.fn.on實現

/**呼叫形式:
 *on(type, [selector], function(e){ ... })
 *on(type, [selector], [data], function(e){ ... })
 *on({ type: handler, type2: handler2, ... }, [selector]) 
 *on({ type: handler, type2: handler2, ... }, [selector], [data])
 */
$.fn.on = function(event, selector, data, callback, one) {
    var autoRemove, delegator, $this = this;
    //event 為物件,批量繫結事件
    if (event && !isString(event)) {
        $.each(event, function(type, fn) {
            $this.on(type, selector, data, fn, one);
        })
        return $this;
    }
    //處理引數
    //沒傳selector引數 callback不是函式,且不為false
    if (!isString(selector) && !isFunction(callback) && callback !== false)
        callback = data, data = selector, selector = undefined;
    //沒傳data
    if (callback === undefined || data === false)
        callback = data, data = undefined;

    if (callback === false) callback = returnFalse;
    // 給每一個Z物件裡面的元素繫結事件
    return $this.each(function(_, element) {
        // 繫結一次,自動解綁
        if (one) autoRemove = function(e) {
                remove(element, e.type, callback);
                return callback.apply(this, arguments);
            }
            //有selector選擇符,使用代理
        if (selector) delegator = function(e) {
                var evt, match = $(e.target).closest(selector, element).get(0);
                if (match && match !== element) {
                    evt = $.extend(createProxy(e), { currentTarget: match, liveFired: element });
                    return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)));
                }
            }
            //繫結事件在這裡
        add(element, event, callback, data, selector, delegator || autoRemove);
    })
}

1.5 核心函式add remove

/**
 * 新增事件的實際方法
 * @param {元素}   element   DOM元素
 * @param {String}   events    事件字串
 * @param {Function} fn        回撥函式
 * @param {All}      data      繫結事件時傳入的data,可以是各種型別   
 * @param {String}   selector  被代理元素的css選擇器
 * @param {[type]}   delegator 進行事件代理的函式
 * @param {[type]}   capture   指定捕獲或者冒泡階段
 */
function add(element, events, fn, data, selector, delegator, capture) {
    var id = zid(element),
        set = (handlers[id] || (handlers[id] = []));
    //多個事件以空格為間隔
    events.split(/\s/).forEach(function(event) {
        //為ready
        if (event == 'ready') return $(document).ready(fn);
        //*************************構建handler*************************
        var handler = parse(event);
        handler.fn = fn;
        handler.sel = selector;

        // emulate mouseenter, mouseleave
        // mouseenter、mouseleave通過mouseover、mouseout來模擬realEvent函式處理
        // hover = { mouseenter: 'mouseover', mouseleave: 'mouseout' }
        if (handler.e in hover) fn = function(e) {
            //http://www.w3school.com.cn/jsref/event_relatedtarget.asp
            // relatedTarget為相關元素,只有mouseover和mouseout事件才有
            // 對mouseover事件而言,相關元素就是那個失去游標的元素;
            // 對mouseout事件而言,相關元素則是獲得游標的元素。
            var related = e.relatedTarget;
            if (!related || (related !== this && !$.contains(this, related)))
                return handler.fn.apply(this, arguments);
        };
        handler.del = delegator;
        // 需要進行事件代理時,呼叫的是封裝了fn的delegator函式
        var callback = delegator || fn;
        handler.proxy = function(e) {
            e = compatible(e); //無第二個引數,其實就是e = e;
            if (e.isImmediatePropagationStopped()) return;
            e.data = data;
            var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args))
                //當事件處理函式返回false時,阻止預設操作和冒泡
            if (result === false) e.preventDefault(), e.stopPropagation();
            return result;
        }
        handler.i = set.length; // 把handler在set中的下標賦值給handler.i
        set.push(handler);
        //*************************構建handler end*************************
        if ('addEventListener' in element)
        //addEventListener -- https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener
        //使用`addEventListener`所傳入的真正回撥函式就是proxy函式
            element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture));
    })
}
//刪除handler
function remove(element, events, fn, selector, capture) {
    var id = zid(element);
    (events || '').split(/\s/).forEach(function(event) {
        findHandlers(element, event, fn, selector).forEach(function(handler) {
            delete handlers[id][handler.i];
            if ('removeEventListener' in element)
                element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture));
        })
    })
}

$.event = { add: add, remove: remove };

1.6 工具函式

1.6.1 zid函式

//通過一個_zid而不是通過DOM物件的引用來連線handler是因為:防止移除掉DOM元素後,
//handlers物件還儲存著對這個DOM元素的引用。通過使用_zid就可以防止這種情況發生,
//避免了記憶體洩漏
function zid(element) {
    return element._zid || (element._zid = _zid++);
}

1.6.2 focus和blur事件的冒泡問題

//focus和blur事件本身是不冒泡的,如果需要對這兩個事件進行事件代理,就要運用一些小技巧。
//首先,如果瀏覽器支援focusin和focusout,就使用這兩個可以冒泡事件來代替。
//如果瀏覽器不支援focusion和focusout,就利用focus和blur捕獲不冒泡的特性,
//傳入addEventListener中的第三個引數設定true,以此來進行事件代理
function eventCapture(handler, captureSetting) {
    return handler.del &&
        (!focusinSupported && (handler.e in focus)) ||
        !!captureSetting;
}

1.6.3 實際傳入到addEventListener第二個引數

// 構建事件代理中的事件物件
function createProxy(event) {
    var key, proxy = { originalEvent: event }; // 新的事件物件有個originalEvent屬性指向原物件
    // 將原生事件物件的屬性複製給新物件,除了returnValue、layerX、layerY和值為undefined的屬性
    // returnValue屬性為beforeunload事件獨有
    for (key in event)
        if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key];
        // 新增eventMethods裡面的幾個方法,並返回新的事件物件
    return compatible(proxy, event);
}

1.6.4 其餘工具函式

// 根據給定的引數在handlers變數中尋找對應的handler
function findHandlers(element, event, fn, selector) {
    event = parse(event); // 解析event引數,分離出事件名和ns
    if (event.ns) var matcher = matcherFor(event.ns);
    // 取出所有屬於element的handler,並且根據event、fn和selector引數進行篩選
    return (handlers[zid(element)] || []).filter(function(handler) {
        return handler && (!event.e || handler.e == event.e) // 事件名不同的過濾掉
            && (!event.ns || matcher.test(handler.ns)) // 名稱空間不同的過濾掉
            && (!fn || zid(handler.fn) === zid(fn)) // 回撥函式不同的過濾掉(通過_zid屬性判斷是否同一個函式)
            && (!selector || handler.sel == selector); // selector不同的過濾掉
    })
}
//解析event引數,如 "click.abc",abc作為ns(名稱空間)
function parse(event) {
    var parts = ('' + event).split('.');
    return { e: parts[0], ns: parts.slice(1).sort().join(' ') }
}
// 生成匹配的namespace表示式:'abc def' -> /(?:^| )abc .* ?def(?: |$)/
function matcherFor(ns) {
    return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)')
}

function realEvent(type) {
    return hover[type] || (focusinSupported && focus[type]) || type;
}

2.取消事件繫結

示例:

 $('body').off('click', '#foo1');
 $.fn.off = function(event, selector, callback) {
     var $this = this;
     if (event && !isString(event)) {
         $.each(event, function(type, fn) {
             $this.off(type, selector, fn);
         })
         return $this;
     }

     if (!isString(selector) && !isFunction(callback) && callback !== false)
         callback = selector, selector = undefined;

     if (callback === false) callback = returnFalse;

     return $this.each(function() {
         remove(this, event, callback, selector);
     })
 }

3.觸發事件

$.Event:建立並初始化一個指定的DOM事件。如果給定properties物件,使用它來擴展出新的事件物件。預設情況下,事件被設定為冒泡方式;這個可以通過設定bubblesfalse來關閉。
通過document.createEvent建立事件物件,然後通過dispatchEvent(原始碼中在$.fn.trigger$.fn.triggerHandler中處理)來出發。

// Create the event.
var event = document.createEvent('Event');

// Define that the event name is 'build'.
event.initEvent('build', true, true);

// Listen for the event.
elem.addEventListener('build', function (e) {
  // e.target matches elem
}, false);

// target can be any Element or other EventTarget.
elem.dispatchEvent(event);

上面有點要注意的就是當建立滑鼠相關的事件時要在document.createEvent的第一個引數中傳入MouseEvents,以提供更多的事件屬性。滑鼠相關的事件指的是:click、mousedown、mouseup和mousemove
示例:

$(document).on('mylib:change', function(e, from, to) {
        console.log('change on %o with data %s, %s', e.target, from, to)
    })
$(document.body).trigger('mylib:change', ['one', 'two'])

原始碼實現:

$.fn.trigger = function(event, args) {
    event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event);
    event._args = args;
    return this.each(function() {
        // handle focus(), blur() by calling them directly
        // 過直接呼叫focus()和blur()方法來觸發對應事件,這算是對觸發事件方法的一個優化
        if (event.type in focus && typeof this[event.type] == "function") {
            this[event.type]();
        }
        // items in the collection might not be DOM elements
        else if ('dispatchEvent' in this) {
            this.dispatchEvent(event);
        } else {
            $(this).triggerHandler(event, args);
        }
    });
};

// triggers event handlers on current element just as if an event occurred,
// doesn't trigger an actual event, doesn't bubble
// 直接觸發事件的回撥函式,而不是直接觸發一個事件,所以也不冒泡
$.fn.triggerHandler = function(event, args) {
    var e, result;
    this.each(function(i, element) {
        e = createProxy(isString(event) ? $.Event(event) : event);
        e._args = args;
        e.target = element;
        $.each(findHandlers(element, event.type || event), function(i, handler) {
            result = handler.proxy(e);
            if (e.isImmediatePropagationStopped()) return false;
        });
    });
    return result;
};
//生成一個模擬事件,如果是滑鼠相關事件,document.createEvent傳入的第一個引數為'MouseEvents'
$.Event = function(type, props) {
    if (!isString(type)) props = type, type = props.type;
    var event = document.createEvent(specialEvents[type] || 'Events'),
        bubbles = true
    if (props)
        for (var name in props)(name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name]);
    event.initEvent(type, bubbles, true);
    return compatible(event);
};

4.bind,unbind,one實現

 $.fn.bind = function(event, data, callback) {
     return this.on(event, data, callback)
 }
 $.fn.unbind = function(event, callback) {
     return this.off(event, callback)
 }
 $.fn.one = function(event, selector, data, callback) {
     return this.on(event, selector, data, callback, 1)
 }

相關推薦

DIY zepto研究zepto原始碼3 -- event模組

上面的博文介紹的都是原始碼src下的zepto.js檔案,接著我們來看看zepto的事件模組,對應檔案是event.js 1.繫結事件 例項Demo <div id="foo1">foo1</div> &

DIY zepto研究zepto原始碼4 -- ajax模組

上面的博文介紹的都是原始碼src下的基礎模組zepto.js檔案和事件模組event.js,下面接著看另外一個獨立的模組–ajax模組ajax.js 1.ajax的過程 當global: true時。在Ajax請求生命週期內,以下這些事件將被觸發。

例mysql主從數據宕機後無法啟動的解決方案

mysql starting 啟動時報錯信息: Starting MySQL... ERROR! The server quit without updating PID file (/usr/local/mysql/data/qkzhi-appzookeeper-1.novalocal.pid

Light libraries是組通用的C基礎目標是為減少重復造輪子而寫(全部用POSIX C實現)

six clas 原子操作 roi 實現 class 動態庫 readme tps Light libraries是一組通用的C基礎庫,目標是為減少重復造輪子而寫實現了日誌、原子操作、哈希字典、紅黑樹、動態庫加載、線程、鎖操作、配置文件、os適配層、事件驅動、工作隊列、RP

如何在 webpack 中引入未模組化的Zepto

前言 最近我在研究多頁面 webpack 模組打包的完整解決方案時,發現用 import 匯入 Zepto 時,會報 Uncaught TypeError: Cannot read property 'createElement' of undefined 錯誤,

用Html5/CSS3做Winform教你搭建CefSharp開發環境(附JavaScript異步調用C#例子及全部源代碼)上

轉載 界面設計 右鍵 異步 一個 由於 編寫 scrip 調用 本文為雞毛巾原創,原文地址:http://www.cnblogs.com/jimaojin/p/7077131.html,轉載請註明 CefSharp說白了就是Chromium瀏覽器的嵌入式核心,我們用此開發W

1.學開發(遊戲賬服數據的使用 Erlang 服務器)

http ats 日誌收集 yield data obj 開發 用戶 nbsp mysql 與mongodb的特點與優劣 http://www.cnblogs.com/eternal1025/p/5419905.html 首先我們來分析下mysql 與mongodb的特

有印為證

有物為證 之前一段時間告誡自己和家人:"路很長也很短,很短也很長,每天進步一點點,自律",最近沈下心態,思索最近半年的工作經歷和當下生活狀態,借用馬未都先生節目中的一句話“觀復嘟嘟,有物為證”稍作修改來描述近來心態變化,那就是“一步一腳印,有印為證”,思考良久其實二者的關系並不矛盾,之前強調的是放慢角度

利用 Siblings實現多個同級div只改變當前點擊的div樣式

ima 可選參數 cto 一個 五個 點擊 rem wid bin 記錄一點,小技巧。直接上代碼嘍,因為今天還沒有功夫扯皮呢。 <!DOCTYPE html> <html> <head&g

微信小程序的認證流程極限工坊教會你

nag 進行 text 根據 服務號 登陸 dad 訂閱號 手機號碼 微信小程序作為一種輕巧靈活的手機應用,改變著手機互聯網形態的同時,也在改變著我們的生活方式。 下面淘小咖具體教大家如何輕松快捷註冊小程序,看圖教程一步一步來! 1、小程序賬號註冊 百度搜索或者直接在公眾平

U盤安裝蘋果系統教程菜鳥也能成大牛

女朋友蘋果電腦遇到問題,女朋友不會整,又怕自己整壞了,就跑去電腦城修,結果被坑了150塊,本來說要雙系統Mac和Windows,結果店家拿著電腦就跑出去了(並不知道幹啥去了),回來發現只有win7,沒有Mac,並且索要正版的價格,我尼瑪,就坑我小女朋友,還好小女朋友沒給他,本來就是盜版系

Spring的前世今生: Spring5.0已經出來了Springboot已經風靡全球 Spring怎麼走過來的 讓我們看看其前世今生~~~

 Spring5.0已經出來了,Springboot已經風靡全球, Spring怎麼一步一步走過來的, 讓我們看看其前世今生~~~   這是關於什麼是Spring Framework,但它是如何開發的?嗯,背後有一個有趣的歷史。讓我們一瞥Spring Framework的

中國正攜區塊鏈下新金融的大棋深圳極可能成為落子的第一

當眾人還在為加密貨幣的漲跌而喋喋不休時,中國已悄然攜區塊鏈下一盤關乎中國新金融新經濟的大棋,而深圳極有可能成為落子的第一步。   金融業務一直被視為區塊鏈技術的第一大應用,8月中旬以來,儘管監管層接連收緊了對加密貨幣的監管,但對區塊鏈在金融創新上的推動卻始終低調而急促,國

Jquery 分頁外掛 帶你接入後臺資料

目錄   一、效果圖 二、分頁 js 原始碼講解 三、分頁樣式 css 原始碼 三、實現前後臺對接 一、效果圖 二、分頁 js 原始碼講解 新建一個 js 檔案,基本直接複製貼上就行,記得引入到需要的頁面中。 需要注意的是: 前面的建構函式

抖音、吃雞、王者榮耀:你的自律是如何被頂級產品經理毀掉的

  作者:書單君 來源:書單(ID:BookSelection)   01 你的沉迷 跟這個時代有關   這是個特別容易沉迷的時代。   抖音、煲劇、王者榮耀、吃雞遊戲……你的時間和注意力悄悄被它們偷走,卻從不說再

(原創)超詳細在eclipse中配置Struts2環境無基礎也能看懂

    (原創)超詳細一步一步在eclipse中配置Struts2環境,無基礎也能看懂  1. 在官網https://struts.apache.org下載Struts2,建議下載2.3系列版本。從圖中可以看出,既可以分開下載子檔案,又可以一次全部下載。 這裡我後

分享我這8年是如何走向架構師的

摘要:心血經驗分享,架構師更多的是一個不斷學習,不斷積累的過程,希望可以幫到同行業的朋友們 前言 成為優秀的架構師是大部分初中級工程師的階段性目標。優秀的架構師往往具備七種核心能力:程式設計能力、除錯能力、編譯部署能力、效能優化能力、業務架構能力、線上運維能力、專案管理能力和規劃能力。 這幾種能力之

自定義實現SpringMvc框架自定義@Controller、@RequestMapping註解自己也是的對程式碼的理解出來的只是比較簡單的例子

1.自定義的DispatcherServlet,就是SpringMvc執行載入類 /*** * 手寫SpringMvc框架<br> * 思路:<br> * 1.手動建立一個DispatcherServlet 攔截專案的所有請求 SpringMv

stm8 觸控使用教程

配套的檔案資料會上傳 1、檢視觸控庫說明文件,根據需要選取晶片(主要支援幾個鍵):STMTouch Driver User Manual 一個channel為一個按鈕,比如STM8L101F 就支援3個按鈕,通過按鈕擴充方式可以增加按鈕,參照: 增加觸控感測按鈕數量