迷你版jQuery——zepto核心源碼分析

前言

zepto號稱迷你版jQuery,而且成爲移動端dom操做庫的首選
事實上zepto不少時候只是借用了jQuery的名氣,保持了與其基本一致的API,其內部實現早已面目全非!
艾倫分析了jQuery,小釵暫時沒有那個本事分析jQuery,這裏就恬不知恥說說本身對zepto的源碼理解,但願對各位有用
首先zepto的出現其實仍是很討巧的,他看見了巨人jQuery在移動浪潮來臨時的轉身慢、牽掛多的問題
立刻搞出了一套輕量級類jQuery框架代碼,核心代碼1000行不到,快速佔領了移動端的市場,因此天下武學無堅不摧,爲快不破啊!!!
也如艾倫所言,jQuery狹義的講其實就是dom操做庫
zepto將這點發揚光大,而且拋棄了瀏覽器兼容的包袱,甚至CSS3的前綴都不給加,這些因素造就了zepto小的事實,因而咱們開始學習他吧
此文只是我的對zepto的粗淺理解,有誤請提出

核心組成

zepto如今也採用了模塊拆分,這樣讀起來其實代碼十分清晰,門檻也低了不少,整個zepto核心模塊保持在900行之內
咱們說他很好的發揚了dom庫特色即是由於這900行基本在幹dom操做的活
核心模塊有如下部分組成:

① 閉包變量、工具類方法定義

這個部分主要爲後面服務,好比說什麼isFunction/isPlainObject/children
其中有一個比較特別的變量是
zepto = {};

這個變量貫穿始終,也是zepto與jQuery很不同的地方,jQuery是一個類,會建立一個個實例,而zepto自己就只是一個對象......javascript

② zepto與jQuery的$

zepto第二階段乾的事情即是定義了一個類
$ = function(selector, context){
  return zepto.init(selector, context)
}

而咱們開始便說了zepto只是一個對象,而zepto.init也僅僅是返回了一個類數組的東西,因而咱們這裏便看到了zepto與jQuery的驚人差別css

第一觀感是zepto沒有類操做!咱們使用$('')的操做返回的也是zepto的實例
$對於zepto來講僅僅是一個方法,zepto卻使用了非正規手法返回了實例......
從這裏看整個zepto其實和jQuery就差距大了,zepto的$方法返回了一個Object的實例,而jQuery的$返回的是真資格的jQuery對象
而從後面看其實zepto也是返回的一個實例可是與jQuery的實現有所不一樣,那麼zepto是怎麼實現實例返回的呢?

③ zepto與jQuery的$.fn

咱們知道jQuery的$.fn指向的是jQuery.prototype的原型對象,而zepto的fn就是一個簡單對象
$.fn = {};
zepto的第三部分即是擴展$函數,咱們使用的$的方法事實上都是其靜態方法,與原型鏈一毛錢關係都沒有
以上即是zepto核心模塊的實現,很乾淨的實現,僅僅是dom操做,不涉及事件或者Ajax操做,簡單來講zepto的實現是這個樣子的
 1 var zepto = {}, $;
 2      
 3 zepto.init = function (selector, context) {
 4   var domArr = [];
 5   //這個__proto__是系統級變量,我以爲zepto不應重置 ,可是不重置的話實例便找不到方法了!!!
 6   domArr.__proto__ = $.fn
 7   domArr.selector = selector;
 8   //一些列操做
 9   return domArr;
10 };
11 
12 $ = function (selector, context) {
13   return zepto.init(selector, context);
14 };
15 
16 $.fn = {
17   addClass: function () { },
18   attr: function () { }
19 };

這裏有段很是關鍵的代碼是:html

domArr.__proto__ = $.fn;
若是是沒有這段代碼的話, domArr即是屬於array的實例,便不能使用$.fn中的方法了,可是他這裏重置了__proto__的指向因此就能用了
PS:因爲IE是不認這個屬性的,因此IE一定會報錯
因爲這裏的改下,原本domArr也會有一些變化:
 1 dom.__proto__.constructor
 2 function Array() { [native code] }
 3 
 4 dom.__proto__.constructor
 5 function Object() { [native code] }
 6 
 7 zepto.Z = function(dom, selector) {
 8   dom = dom || []
 9   dom.__proto__ = $.fn
10   dom.selector = selector || ''
11   return dom
12 }
13 //最後加上一句:
14 zepto.Z.prototype = $.fn
如此一來,咱們全部的$方法返回的東西其實就變成了zepto.Z的實例了,這裏的實現原理其實也有點繞口:
構造函數zepto.Z 包含一個原型 $.fn(zepto.Z的prototype被重寫了)
原型$.fn具備一個Constructor回值構造函數zepto.Z(這裏因爲其粗暴的幹法其實直接指向了Object,這裏關係其實已經丟失)
比較不正經的是竟然是經過重寫__proto__實現,感受怪怪的,好了核心模塊介紹結束,咱們便進入入口函數的解析了

分解$方法

$是zepto的入口,具備兩個參數selector選擇器與context選擇範圍,這裏看着是兩個參數,事實上各個參數不一樣會形成不一樣的實現
$方法至關於一個黑盒子,用戶會根據本身的想法得到本身想要的結果,這也會致使$的實現變得複雜:
 1 $('div');
 2 //=> all DIV elements on the page
 3 $('#foo');
 4 //=> element with ID "foo"
 5 
 6 // create element:
 7 $("<p>Hello</p>");
 8 //=> the new P element
 9 // create element with attributes:
10 $("<p />", {
11   text: "Hello",
12   id: "greeting",
13   css: { color: 'darkblue' }
14 });
15 //=> <p id=greeting style="color:darkblue">Hello</p>
16 
17 // execute callback when the page is ready:
18 $(function ($) {
19   alert('Ready to Zepto!')
20 });

咱們如今來分析其每一種實現java

選擇器

zepto主要乾的事情仍是作dom選擇,這裏包括標籤選擇、id選擇、類選擇等,少了sizzle的複雜,直接使用了querySelectorAll的實現真的很偷懶
PS:同一個頁面出現相關相同id的話querySelectorAll會出BUG,這個你們要當心處理!!!
這裏篩選的流程是:
① 執行$(selector)方法
② 執行zepto.init(selector)方法,init裏面的邏輯就有點小複雜了
判斷selector是否是一個字符串,這裏須要是乾淨的字符串,而且context爲undefined(這裏差距不大,了不得是查找範圍的問題)
③ 通過上述邏輯處理,高高興興進入zepto.qsa(document, selector)邏輯
這裏的邏輯比較簡單直接調用判斷下選擇器的類型(id/class/標籤)就直接使用對應的方法獲取元素便可
zepto.qsa = function(element, selector){
  var found,
      maybeID = selector[0] == '#',
      maybeClass = !maybeID && selector[0] == '.',
      nameOnly = maybeID || maybeClass ? selector.slice(1) : selector, // Ensure that a 1 char tag name still gets checked
      isSimple = simpleSelectorRE.test(nameOnly)
  return (isDocument(element) && isSimple && maybeID) ?
    ( (found = element.getElementById(nameOnly)) ? [found] : [] ) :
    (element.nodeType !== 1 && element.nodeType !== 9) ? [] :
    slice.call(
      isSimple && !maybeID ?
        maybeClass ? element.getElementsByClassName(nameOnly) : // If it's simple, it could be a class
        element.getElementsByTagName(selector) : // Or a tag
        element.querySelectorAll(selector) // Or it's not simple, and we need to query all
    )
}
View Code

建立元素

$方法的第二大功能即是建立元素了,好比咱們這裏的
$("<p>Hello</p>");

這裏依舊會通過zepto.init的處理,判斷是否具備尖括號(<),有的話便會進入神奇的fragment邏輯建立文檔碎片node

dom = zepto.fragment(selector, RegExp.$1, context)
這裏有一個正則表達式對傳入的html進行解析,目標是標籤名
PS:zepto對p標籤的解析也會出問題,不建議使用
zepto.fragment = function(html, name, properties) {}

到fragment方法時,會傳入html和那麼而且會有相關屬性,可是咱們通常不這樣幹,僅僅但願建立DOMweb

zepto.fragment = function(html, name, properties) {
  var dom, nodes, container

  // A special case optimization for a single tag
  if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))

  if (!dom) {
    if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>")
    if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
    if (!(name in containers)) name = '*'

    container = containers[name]
    container.innerHTML = '' + html
    dom = $.each(slice.call(container.childNodes), function(){
      container.removeChild(this)
    })
  }

  if (isPlainObject(properties)) {
    nodes = $(dom)
    $.each(properties, function(key, value) {
      if (methodAttributes.indexOf(key) > -1) nodes[key](value)
      else nodes.attr(key, value)
    })
  }

  return dom
}
View Code
裏面的邏輯各位本身去看,我這裏很少說了,仍是很簡單的,大概的想法是
建立一個空的div元素,將字符串裝載,而後遍歷div的子元素,最後返回一個node的集合數組,這個也就是咱們實際須要的......
這個樣子,建立標籤或者selector選擇器獲得的結果是一致的
其它邏輯大同小異,咱們直接就過了,zepto核心入口邏輯就到此結束了......

fn的實現

fn中包含了zepto的不少功能,要一一說明就多了去了,首先由$擴展開始說
除了原型擴展外還爲$包含了不少靜態方法,好比什麼uuid,isFunction,而後就開始了原型鏈擴展之路
$.fn與zepto.Z.prototype指向的是同一空間,這裏達到了是擴展原型鏈的效果
這裏抽2個經常使用API來看看,好比這裏的attr
attr: function(name, value){
  var result
  return (typeof name == 'string' && value === undefined) ?
    (this.length == 0 || this[0].nodeType !== 1 ? undefined :
      (name == 'value' && this[0].nodeName == 'INPUT') ? this.val() :
      (!(result = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : result
    ) :
    this.each(function(idx){
      if (this.nodeType !== 1) return
      if (isObject(name)) for (key in name) setAttribute(this, key, name[key])
      else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name)))
    })
},
function setAttribute(node, name, value) {
  value == null ? node.removeAttribute(name) : node.setAttribute(name, value)
}
咱們看到他這裏直接將其轉換爲了元素DOM操做,沒有什麼好說的,只是若是value不爲undefined時,裏面有一個循環爲屬性賦值的動做
再看這裏的html接口
html: function(html){
  return arguments.length === 0 ?
    (this.length > 0 ? this[0].innerHTML : null) :
    this.each(function(idx){
      var originHtml = this.innerHTML
      $(this).empty().append( funcArg(this, html, idx, originHtml) )
    })
},
function funcArg(context, arg, idx, payload) {
  return isFunction(arg) ? arg.call(context, idx, payload) : arg
}
這裏實際上是先將this清空,而後裝載新的dom結構,這裏與設置innerHTML有所不一樣,append會執行其中的js,設置innerHTML不會執行
剩下的接口有興趣的朋友本身去看吧,zepto這裏實現仍是比較簡單的。
這裏值得一說的是,一些API你直接去看可能看不懂,這個時候就動手寫寫,實現相同的功能,而後對代碼進行重構,最後重構下來代碼和他寫的就差很少了,這裏並非代碼難,而是他那種寫法不太好看。

事件實現

一個稍微成熟點的框架或者說稍微成熟點的團隊,通常會對原生的一些東西進行封裝,緣由是他們可能須要擴展很是典型的例子即是事件與settimeout
以setTimeout爲例,在webapp中每次view的切換應該清理全部的settimeout,可是咱們知道clearTimeout()是必須傳入id的,因此咱們不能這麼幹
如今回到javascript事件這塊,最初事件的出現可能僅僅是爲了作瀏覽器兼容
那麼如今咱們依舊會使用zepto提供的事件主要緣由就是其擴展的一些功能,好比委託與命名空間等,最重要的仍是事件句柄移除
javascript事件的移除非常嚴苛,要求必須與之一致的參數,好比:
el.addEventListerner(type, fn, capture);
el.removeEventListerner(type, fn, capture);

二者參數須要徹底一致,而咱們的fn不少時候就是個匿名函數甚至是對象,不少時候定義後句柄引用就丟了,咱們根本無法將其保持一致正則表達式

這個時候這個句柄便沒法釋放,因此咱們須要對事件進行封裝,咱們這裏便進入zepto event的實現,學習這個仍是看入口點

事件註冊

簡單來講使用zepto綁定事件通常是這樣:數組

① $.on(type, fn)
② $.bind(type, fn)
③ $.click(fn)
④ ......
事實上,這些方式差距不大,特別是第二種只是作了一個語法糖,好比:
$.click = function (fn) {
    return this.bind('click', fn);
}

事實上他仍是調用的$.bind實現事件綁定,換個思惟方式,其實整個zepto事件實現能夠濃縮成這麼幾句話:瀏覽器

var eventSet = {
    el: {fnType: []}
};
function on(type, fn) {}
function off(type, fn) {}
這個即是zepto事件核心代碼......固然這裏還差了一個trigger,這裏即是與傳統自建系統不同的地方,他的觸發是經過瀏覽器處理
這個是一個標準的發佈訂閱系統,咱們對瀏覽器的操做會生產事件,這個時候瀏覽器會根據咱們的行爲通知對應的事件接收者處理事件
全部的綁定最終調用的皆是$.on,而on或者off的最終歸宿爲局部閉包add和remove方法
$.fn.on = function(event, selector, data, callback, one){
  var autoRemove, delegator, $this = this
  if (event && !isString(event)) {
    $.each(event, function(type, fn){
      $this.on(type, selector, data, fn, one)
    })
    return $this
  }

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

  if (callback === false) callback = returnFalse

  return $this.each(function(_, element){
    if (one) autoRemove = function(e){
      remove(element, e.type, callback)
      return callback.apply(this, arguments)
    }

    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)
  })
}
View Code

這裏的event能夠是以空格分隔的字符串,通常狀況下是單一的事件閉包

event => 'mousedown touchstart'
event => 'click'
而後這裏開始了處理邏輯:
① 參數處理
第一步固然是作參數處理,會修正參數,好比你沒有傳事件句柄,這裏會給個默認的,而後開始循環綁定,由於咱們使用$()返回的是一個數組
進入循環邏輯後,this與element即是真資格的dom元素了,未經雕琢,開始是對one的處理,咱們不予關注,繼續向下便進入第一個關鍵點
簡單狀況下咱們的selector爲undefined,因此這裏錯過了一個事件委託的重要邏輯,咱們先不予理睬,再往下便進入了閉包方法add了
這個狀況下selector與delegator爲undefined,僅僅是前3個參數有效

add在event事件中扮演了重要的角色

function add(element, events, fn, data, selector, delegator, capture){
  var id = zid(element), set = (handlers[id] || (handlers[id] = []))
  events.split(/\s/).forEach(function(event){
    if (event == 'ready') return $(document).ready(fn)
    var handler = parse(event)
    handler.fn = fn
    handler.sel = selector
    // emulate mouseenter, mouseleave
    if (handler.e in hover) fn = function(e){
      var related = e.relatedTarget
      if (!related || (related !== this && !$.contains(this, related)))
        return handler.fn.apply(this, arguments)
    }
    handler.del = delegator
    var callback = delegator || fn
    handler.proxy = function(e){
      e = compatible(e)
      if (e.isImmediatePropagationStopped()) return
      e.data = data
      var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args))
      if (result === false) e.preventDefault(), e.stopPropagation()
      return result
    }
    handler.i = set.length
    set.push(handler)
    if ('addEventListener' in element)
      element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
  })
}
View Code

第一段代碼就很重要:

var id = zid(element)
function zid(element) {
  return element._zid || (element._zid = _zid++)
}

這裏的zid很是關鍵,這裏的element爲與原生對象,這裏在上面加了一個_zid的屬性,這個屬性會跟隨其由始至終,不會丟失,若是是zepto封裝的dom對象的話,就很容易丟失,由於每次根據$()建立的dom都是新的,這個_zid放到原生屬性上是頗有意義的

第二個變量也很關鍵:
set = (handlers[id] || (handlers[id] = []))

咱們全部綁定的事件以_zid爲鍵值放在了外部閉包環境handlers對象中,每個id對應的爲一個數組,這個與綁定前後順序相關

而後進入具體綁定邏輯:
完了這裏會考慮是'mousedwon touchstart'的狀況因此會有一個循環,咱們這裏因爲只是click便不予理睬了,ready事件咱們也直接忽略,進入邏輯後關鍵點來了
這裏定義了一個handler對象,這個對象會存於handlers裏面
var handler = parse(event)
handler.fn = fn
handler.sel = selector

function parse(event) {
  var parts = ('' + event).split('.')
  return {e: parts[0], ns: parts.slice(1).sort().join(' ')}
}
這裏會解析event參數,取出其中的命名空間,好比:'click.ui'或者'click.namespace'
返回的對象,第一個是真正綁定的事件Type,第二個是其命名空間:
handler = {
  e: 'click',
  ns: ''//我這裏爲null  
}
後面再爲handler對象擴展fn與selector屬性,這裏的fn尤爲關鍵!!!
咱們知道,綁定時如果使用的是匿名函數的話,其引用會丟失,可是這裏就把他保持下來存到了handlers中,爲後面off消除句柄提供了條件
下面會有段代碼,處理mouse事件,用以模擬mouseenter, mouseleave,咱們簡單來看看其實現:
// emulate mouseenter, mouseleave
if (handler.e in hover) fn = function(e){
  var related = e.relatedTarget
  if (!related || (related !== this && !$.contains(this, related)))
    return handler.fn.apply(this, arguments)
}
$.contains = function(parent, node) {
  return parent !== node && parent.contains(node)
}

relatedTarget 事件屬性返回與事件的目標節點相關的節點。
對於 mouseover 事件來講,該屬性是鼠標指針移到目標節點上時所離開的那個節點。
對於 mouseout 事件來講,該屬性是離開目標時,鼠標指針進入的節點。
對於其餘類型的事件來講,這個屬性沒有用。
因此咱們使用mouseenter,其實mousemove依舊一直在執行,只不過知足要求才會進入mouseleave綁定的回調

這裏結束便進入事件綁定的真正邏輯,這裏又爲handler新增了一個proxy屬性,將真實的事件回調封裝了,封裝的主要緣由是作事件代理,事件代理一塊咱們先不關注
咱們看到proxy將咱們的回調fn(已經變成了callback),作一次封裝,直接爲element註冊事件了,其影響會在觸發時產生:
function compatible(event, source) {
  if (source || !event.isDefaultPrevented) {
    source || (source = event)

    $.each(eventMethods, function(name, predicate) {
      var sourceMethod = source[name]
      event[name] = function(){
        this[predicate] = returnTrue
        return sourceMethod && sourceMethod.apply(source, arguments)
      }
      event[predicate] = returnFalse
    })

    if (source.defaultPrevented !== undefined ? source.defaultPrevented :
        'returnValue' in source ? source.returnValue === false :
        source.getPreventDefault && source.getPreventDefault())
      event.isDefaultPrevented = returnTrue
  }
  return event
}
View Code

觸發事件時他這裏首先會對事件參數event作一次封裝返回,首先將三大事件對象進行新增接口

這裏重置的一個緣由是處理stopImmediatePropagation不支持的瀏覽器
而後會執行真正的回調,這裏會傳入相關參數,並將做用域指向element,因而事件註冊到事件定義第一階段結束
不同的是事件委託,好比:
el1.on('click', '#Div1', function (e) {
  s = '';
});

具備selector參數後在add處便會處理不一致,會多出一段邏輯將真正的回調重置了

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中依舊會被再次處理),首先這裏比較關鍵的代碼是

match = $(e.target).closest(selector, element).get(0)
這個會根據當前點擊最深節點與selector選擇器選擇離他最近的parent節點,而後判斷是否找到,這裏條件還必須知足找到的不是當前元素
若是找到了,會對event參數作一次處理,爲其重寫currentTarget屬性,讓他指向與selector相關的節點(這點很關鍵)
function createProxy(event) {
  var key, proxy = { originalEvent: event }
  for (key in event)
    if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key]

  return compatible(proxy, event)
}
這裏能夠看到,咱們若是爲document下面的三個元素綁定事件代理,每次點擊幾回便會執行幾回事件,只不過會判斷是否進入處理邏輯而已
這裏舉個div與span的例子,若是父級div(wrapper)下面分別爲div和span綁定事件的話
$('#wrapper').on('click', '#span', fn);
$('#wrapper').on('click', '#div', fn);
這個事實上會爲爲wrapper綁定兩個click事件,咱們每次點擊wrapper區域都會執行兩次click事件,可是是否執行span或者div的事件,要看這裏是否點擊到了其子節點(e.target)
這裏處理結束後會進入add方法,與剛剛的邏輯一致,咱們便不予理睬了,只是事件代理的狀況下event參數連續被compatible了,而原始的事件句柄也被包裹了兩層

事件移除

事件綁定說完,事件移除便比較簡單了,入口是off,統一處理存於閉包remove方法中
$.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)
  })
}
View Code

代碼比較簡單,能夠直接進入remove的邏輯

這裏有一點值得注意的是,這裏的this指向的是原生dom,而且你們注意到裏面的_zid,callback或者selector咱們通常不使用
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))
    })
  })
}

事件註冊邏輯複雜,刪除卻只須要幾行,在remove時,這裏會根據元素的_zid而後調用findHandlers取出存於閉包handlers裏面的事件對象

 1 function findHandlers(element, event, fn, selector) {
 2   event = parse(event)
 3   if (event.ns) var matcher = matcherFor(event.ns)
 4   return (handlers[zid(element)] || []).filter(function(handler) {
 5     return handler
 6       && (!event.e || handler.e == event.e)
 7       && (!event.ns || matcher.test(handler.ns))
 8       && (!fn || zid(handler.fn) === zid(fn))
 9       && (!selector || handler.sel == selector)
10   })
11 }

這裏有個很是巧妙的地方是咱們能夠根據以前的namespace取出咱們註冊的事件集合,好比:

findHandlers處理結束便進入最後的的句柄移除操做便可
而這裏能移除句柄的關鍵又是在於以前將事件句柄handler.proxy保存下來的緣由,至此整個event邏輯結束,值得注意的是element的_zid標識還在,
至於trigger簡單來講即是建立一個event事件對象而後dispatch,僅此而已

手勢處理

zepto提供了一個touch庫進行手勢事件的補充,不得不說其中一個實現頗有問題,會形成一些莫名其妙的BUG,但只是以代碼實現來講仍是很清晰的
zepto的touch庫代碼約150行,其實現方案是:
在載入zepto後爲document綁定touchstart、touchmove、touchend事件,根據手指x、y值的位置判斷方向從而觸發tap、doubleTap、swipeLeft等事件,這裏有幾個使人不爽的地方:
① 一旦引入該庫便在全局綁定事件,每次點擊皆會觸發無心義的tap事件
② 如果有人2B的重複引入了zepto事件,那麼tap類型事件會觸發兩次,這個會產生BUG
③ zepto爲了實現doubleTap等功能,2B的在touchend時候設置了一個settimeout,而後整個世界都充滿翔了
因爲setTimeout的拋出主幹流程,致使其event參數失效,這個時候就算在tap中執行e.preventDefault()或者什麼都是無效的,這個是致使zepto tap「點透」的罪魁禍首
因此咱們如果僅僅爲了某塊區域的手勢功能,徹底沒有必要引入zepto庫,得不償失的,咱們能夠如下面代碼簡單替換,再複雜的功能就無法了:
(function () {

    //偏移步長
    var step = 20;

    var touch = {};
    var down = 'touchstart';
    var move = 'touchmove';
    var up = 'touchend';
    if (!('ontouchstart' in window)) {
      down = 'mousedown';
      move = 'mousemove';
      up = 'mouseup';
    }

    //簡單借鑑ccd思惟作簡要處理
    function swipeDirection(x1, x2, y1, y2, sensibility) {

      //x移動的步長
      var _x = Math.abs(x1 - x2);
      //y移動步長
      var _y = Math.abs(y1 - y2);
      var dir = _x >= _y ? (x1 - x2 > 0 ? 'left' : 'right') : (y1 - y2 > 0 ? 'up' : 'down');

      //設置靈敏度限制
      if (sensibility) {
        if (dir == 'left' || dir == 'right') {
          if ((_y / _x) > sensibility) dir = '';
        } else if (dir == 'up' || dir == 'down') {
          if ((_x / _y) > sensibility) dir = '';
        }
      }
      return dir;
    }

    //sensibility設置靈敏度,值爲0-1
    function flip(el, dir, fn, noDefault, sensibility) {
      if (!el) return;

      el.on(down, function (e) {
        var pos = (e.touches && e.touches[0]) || e;
        touch.x1 = pos.pageX;
        touch.y1 = pos.pageY;

      }).on(move, function (e) {
        var pos = (e.touches && e.touches[0]) || e;
        touch.x2 = pos.pageX;
        touch.y2 = pos.pageY;

        //若是view過長滑不動是有問題的
        if (!noDefault) { e.preventDefault(); }
      }).on(up, function (e) {


        if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > step) ||
        (touch.y2 && Math.abs(touch.y1 - touch.y2) > step)) {
          var _dir = swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2, sensibility);
          if (dir === _dir) {
            typeof fn == 'function' && fn();
          }
        } else {
          //tap的狀況
          if (dir === 'tap') {
            typeof fn == 'function' && fn();
          }
        }
      });
    }

    function flipDestroy(el) {
      if (!el) return;
      el.off(down).off(move).off(up);
    }

    _.flip = flip;
    _.flipDestroy = flipDestroy;

})();
View Code

其它

累了,略......

Ajax

animate

結語

咱們今天對zepto作了一個整理性學習,但願對各位有幫助,最後微博求粉!!!
相關文章
相關標籤/搜索