迷你版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的 ObjectjQuery返回的是真资格的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 >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
    )
}
复制代码

建立元素

$方法的第二大功能即是建立元素了,好比咱们这里的
$("<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
}
复制代码
里面的逻辑各位本身去看,我这里很少说了,仍是很简单的,大概的想法是
建立一个空的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)
  })
}
复制代码

这里的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))
  })
}
复制代码

第一段代码就很重要:

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
}
复制代码

触发事件时他这里首先会对事件参数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)
  })
}
复制代码

代码比较简单,能够直接进入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;

})();
复制代码

其它

累了,略......

Ajax

animate

结语

咱们今天对zepto作了一个整理性学习,但愿对各位有帮助,最后微博求粉!!!
相关文章
相关标签/搜索