迷你版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本身就只是一個對象......

② zepto與jQuery的$

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

而我們開始便說了zepto只是一個對象,而zepto.init也僅僅是返回了一個類數組的東西,於是我們這里便看到了zepto與jQuery的驚人差異

第一觀感是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 };

這里有段非常關鍵的代碼是:

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 });

我們現在來分析其每一種實現

選擇器

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邏輯創建文檔碎片

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

到fragment方法時,會傳入html和那么並且會有相關屬性,但是我們一般不這樣干,僅僅希望創建DOM

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做了一個整理性學習,希望對各位有幫助,最后微博求粉!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM