https://www.cnblogs.com/diver-blogs/p/5657323.html  地址

fastclick.js源码解读分析

  阅读优秀的js插件和库源码,可以加深我们对web开发的理解和提高js能力,本人能力有限,只能粗略读懂一些小型插件,这里带来对fastclick源码的解读,望各位大神不吝指教~!

fastclick诞生背景与使用

  在解读源码前,还是简单介绍下fastclick:

诞生背景

  我们都知道,在移动端页面开发上,会出现一个问题,click事件会有300ms的延迟,这让用户感觉很不爽,感觉像是网页卡顿了一样,实际上,这是浏览器为了更好的判断用户的双击行为,移动浏览器都支持双击缩放或双击滚动的操作,比如一个链接,当用户第一次点击后,浏览器不能立刻判断用户确实要打开这个链接,还是想要进行双击的操作,因此几乎现在所有浏览器都效仿Safari当年的约定,在点击事件上加了300毫秒的延迟。

  就因为这300ms的延迟,催生了fastclick的诞生~

使用方法

  1.引入fastclick到自己的开发环境(源码第829~840行,后面都采用简写了哈,如:829~840)

1
2
3
4
5
6
7
8
9
10
11
12
13
//优先兼容AMD方式
if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
  define(function() {
    return FastClick;
  });
else if (typeof module !== 'undefined' && module.exports) {
  //兼容commonJs风格
  module.exports = FastClick.attach;
  module.exports.FastClick = FastClick;
else {
  //最后兼容原生Js 
  window.FastClick = FastClick;
}  

  fastclick的引入兼容AMD、commonJs风格、原生Js的方式,在本人的大半年开发过程中,只接触过commonJs的风格,这里就不多做介绍了,根据自己项目技术栈选择吧~

  2.入口函数(824~826)

1
2
3
4
5
6
//layer参数:要监听的dom对象,一般是document.body
//options参数:用来覆盖自定义参数,个人建议不去覆盖,
//因为里面的参数设定都是FastClick的精华,不要着急,参数在后面会详细介绍
FastClick.attach = function(layer, options) {
  return new FastClick(layer, options);
};

  我们如果要使用fastclick的话,只需要在自己的js上写上FastClick.attach(document.body),这样就可以了,没错,就是这么简单!

fastclick源码解读

  

  判断是否需要调用FastClick(105~107)

  fastclick在某些情况下是不需要的,当然fastclick的开发者早已经替我们想到了,在官网上有详细的解释,如果你想详细了解,请点击这里

1
2
3
4
//所有在不需要FastClick的浏览器会直接return掉,不会执行fastclick.js后面的代码。
if (FastClick.notNeeded(layer)) {
  return;
}

  参数解读(23~103)

  上面提到了入口函数中的options参数,这里不得不赞一下fastclick的源码,对每个参数都做出了详细的解释(虽然都是英文,但很容易懂),这里介绍几个我认为比较精华的参数,如下代码:

1
2
3
4
5
6
//比如这几个参数,上面提到不建议自定义覆盖,
//这些参数正是FastClick的精华所在,
//大幅度修改数值可能让整个库的功效大打折扣。
this.touchBoundary = options.touchBoundary || 10;
this.tapDelay = options.tapDelay || 200;
this.tapTimeout = options.tapTimeout || 700;

  touchBoundary: 这个参数是用于判断用户触摸屏幕后,移动的距离,如果大于10px,那么就不被看做是一次点击事件(具体实现后面介绍,下面的参数也同样会解释)。

  tapDelay: 这个参数规定了touchstart和touchend事件之间的200毫秒最小间隔,如果在这段时间内,发生了第二次点击将会被阻止。

  tapTimeout: 这个参数规定了一次tap事件(源码解释为tap事件)最长的事件,即touchstart和touchend事件之间的700毫秒最大间隔,超过这个时间,将不会被视作tap事件。

  当然还有很多参数,因为篇幅的关系,这里就不一一解释了,也不贴出源码,如果你想了解更多,请下载并阅读源码23~103行,每个参数都有详细的解释,只要学过高中英语都能读得懂- -(我四级没过都能读得懂。。。)

   主干部分解读(23~174)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
function FastClick(layer, options) {
        var oldOnClick;
 
        options = options || {};
 
        //这里本来是定义了一些参数的,但我在之前讲过了,这里的代码被我删掉了
 
        //如果是属于不需要处理的元素类型,则直接返回,notNeeded方法已在上方提到
        if (FastClick.notNeeded(layer)) {
            return;
        }
 
        //语法糖,兼容一些用不了 Function.prototype.bind 的旧安卓
        //所以后面不走 layer.addEventListener('click', this.onClick.bind(this), true);而是调用的这里的bind方法
        function bind(method, context) {
            return function() { return method.apply(context, arguments); };
        }
 
 
        var methods = ['onMouse''onClick''onTouchStart''onTouchMove''onTouchEnd''onTouchCancel'];
        var context = this;
        for (var i = 0, l = methods.length; i < l; i++) {
            context[methods[i]] = bind(context[methods[i]], context);//调用上面定义的bind()方法
        }
 
        //绑定事件,安卓需要做额外处理
        if (deviceIsAndroid) {
            layer.addEventListener('mouseover'this.onMouse, true);
            layer.addEventListener('mousedown'this.onMouse, true);
            layer.addEventListener('mouseup'this.onMouse, true);
        }
 
        layer.addEventListener('click'this.onClick, true);
        layer.addEventListener('touchstart'this.onTouchStart, false);
        layer.addEventListener('touchmove'this.onTouchMove, false);
        layer.addEventListener('touchend'this.onTouchEnd, false);
        layer.addEventListener('touchcancel'this.onTouchCancel, false);
 
        // 兼容不支持 stopImmediatePropagation 的浏览器
        if (!Event.prototype.stopImmediatePropagation) {
            layer.removeEventListener = function(type, callback, capture) {
                var rmv = Node.prototype.removeEventListener;
                if (type === 'click') {
                    rmv.call(layer, type, callback.hijacked || callback, capture);
                else {
                    rmv.call(layer, type, callback, capture);
                }
            };
 
            layer.addEventListener = function(type, callback, capture) {
                var adv = Node.prototype.addEventListener;
                if (type === 'click') {
                    //留意这里 callback.hijacked 中会判断 event.propagationStopped 是否为真来确保(安卓的onMouse事件)只执行一次
                    //在 onMouse 事件里会给 event.propagationStopped 赋值 true
                    adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
                            if (!event.propagationStopped) {
                                callback(event);
                            }
                        }), capture);
                else {
                    adv.call(layer, type, callback, capture);
                }
            };
        }
 
        // 如果layer直接在DOM上写了 onclick 方法,那我们需要把它替换为 addEventListener 绑定形式
        if (typeof layer.onclick === 'function') {
            oldOnClick = layer.onclick;
            layer.addEventListener('click'function(event) {
                oldOnClick(event);
            }, false);
            layer.onclick = null;
        }
    }

  在fastclick的主干部分,主要做了这么几件事情:
  1.定义一些参数,在后面的代码中会用到,作用已在前面提过。

  2.判断是否需要使用fastclick。

  3.绑定了事件:注意,这里绑定的都是fastclick中定义的事件,并不是原生事件,因为使用bind()方法做了处理,事件回调中的this都是fastclick实例上下文。

  4.兼容不支持 stopImmediatePropagation 的浏览器。

  5.将dom上写的onclick方法替换为addEventListener绑定形式

  核心部分解读(包括核心部分涉及到的方法)

  下面代码中的注释是我自己的理解,如有不对的地方请各位阅读者指出~~  

  1.onTouchStart(391-450): 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
FastClick.prototype.onTouchStart = function(event) {
        var targetElement, touch, selection;
 
        // 如果是多点触摸,将被忽略,直接返回true,不会执行后面代码
        if (event.targetTouches.length > 1) {
            return true;
        }
        //获得触摸对象,这个getTargetElementFromEventTarget方法将稍后讲解
        targetElement = this.getTargetElementFromEventTarget(event.target);
        touch = event.targetTouches[0];
                //判断系统是否为ios
        if (deviceIsIOS) {
 
            // 在ios中,受信任的事件将会被取消,返回true。相关知识:如果一个事件是由设备本身(如浏览器)触发的,而不是通过JavaScript模拟合成的,那个这个事件被称为可信任的(trusted)
            //获得激活选中区
            selection = window.getSelection();
            //判断是否有range被选中&&选中“起点”和“结束点”是否重合,这一部分我猜测应该是ios自带的复制文字效果,为了防止用户意图复制文字时触发tap事件。
            if (selection.rangeCount && !selection.isCollapsed) {
                return true;
            }
//这一部分应该是对ios4中的bug进行处理吧,不过现在也没什么人用ios4这种古董系统,所以注释我就不翻译了,有兴趣自己去了解吧~
            if (!deviceIsIOS4) {
 
                // Weird things happen on iOS when an alert or confirm dialog is opened from a click event callback (issue #23):
                // when the user next taps anywhere else on the page, new touchstart and touchend events are dispatched
                // with the same identifier as the touch event that previously triggered the click that triggered the alert.
                // Sadly, there is an issue on iOS 4 that causes some normal touch events to have the same identifier as an
                // immediately preceeding touch event (issue #52), so this fix is unavailable on that platform.
                // Issue 120: touch.identifier is 0 when Chrome dev tools 'Emulate touch events' is set with an iOS device UA string,
                // which causes all touch events to be ignored. As this block only applies to iOS, and iOS identifiers are always long,
                // random integers, it's safe to to continue if the identifier is 0 here.
                if (touch.identifier && touch.identifier === this.lastTouchIdentifier) {
                    event.preventDefault();
                    return false;
                }
 
                this.lastTouchIdentifier = touch.identifier;
 
                // If the target element is a child of a scrollable layer (using -webkit-overflow-scrolling: touch) and:
                // 1) the user does a fling scroll on the scrollable layer
                // 2) the user stops the fling scroll with another tap
                // then the event.target of the last 'touchend' event will be the element that was under the user's finger
                // when the fling scroll was started, causing FastClick to send a click event to that layer - unless a check
                // is made to ensure that a parent layer was not scrolled before sending a synthetic click (issue #42).
                this.updateScrollParent(targetElement);
            }
        }
 
//记录click已经发生,这也是一个参数哟!
        this.trackingClick = true;
//记录click发生的时间戳,参数一员
        this.trackingClickStart = event.timeStamp;
//记录click的目标对象,参数一员
        this.targetElement = targetElement;
 
//这里不解释,你们懂得
        this.touchStartX = touch.pageX;
        this.touchStartY = touch.pageY;
 
        //防止200ms内的多次点击,tapDelay这个参数在上面提到过
        if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
            event.preventDefault();
        }
 
        return true;
    };

  

1
2
3
4
5
6
7
8
9
FastClick.prototype.getTargetElementFromEventTarget = function(eventTarget) {
 
        // 在一些旧的浏览器(尤其是Safari浏览器在iOS4.1)事件目标可能是一个文本节点。那么这个时候返回它的父节点。(⊙o⊙)…涨知识,不过目前来看可能这种情况很少了。
        if (eventTarget.nodeType === Node.TEXT_NODE) {
            return eventTarget.parentNode;
        }
 
        return eventTarget;
    };

 onTouchStart这个单词,很容易让我们知道fastclick中的tap仍然是通过touch事件进行模拟的,在touchStart时,fastclick主要做了这么几件事:

  1.忽略了多点触摸的情况

  2.解决了一些兼容性问题(ios4 和 ios复制文字效果)

   3.追踪click事件,获得click对象,记录了发生click事件时的时间戳

  4.防止200ms内的多次点击

  这里其实有点乱,因为其实是touch事件,但是为什么记作click事件呢(有的时候又说是tap事件),我们可以这样理解:本质上发生是touch事件,而fastclick要根据touch事件模拟click(tap)事件,这有一些条件,当该次触摸事件符合条件时,便可以认为是一次click事件,tap事件就是相对于pc端的click事件,所以移动端tap事件==pc端click事件。恩,因为源码中用到了trackingClickStart和一些带click的参数,所以你们懂的。tap事件本身是不存在的,是一种合成事件。

  2.onTouchMove(476~488)

1
2
3
4
5
6
7
8
9
10
11
12
13
FastClick.prototype.onTouchMove = function(event) {
        if (!this.trackingClick) {
            return true;
        }
 
        // 如果touchMove超过了规定距离(10px),那么取消追踪这次touch事件,不会被模拟为tap,可以理解为:用户手指在滑动屏幕。。
        if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) {
            this.trackingClick = false;
            this.targetElement = null;
        }
 
        return true;
    };

  

1
2
3
4
5
6
7
8
9
FastClick.prototype.touchHasMoved = function(event) {
        var touch = event.changedTouches[0], boundary = this.touchBoundary;
      //这里就是判断touchMove移动的距离(x轴和y轴)是否超过boundary(10px),超过返回true    
        if (Math.abs(touch.pageX - this.touchStartX) > boundary || Math.abs(touch.pageY - this.touchStartY) > boundary) {
            return true;
        }
 
        return false;
    };

onTouchMove很明显就是在触摸过程中手指发生位移触发的事件,fastclick在这里主要做了两件事:

  1.首先判断是否有符合条件的tranckingClick,tranck意思是追踪,就是在onTouchStart阶段提供的判断条件,条件通过那么该次touch事件将被追踪,记作tranckingClick。

  2.如果touchMove超过了规定距离(x轴或y轴10px),那么取消追踪这次touch事件,不会被模拟为tap,可以理解为:用户手指在滑动屏幕。。

  3.onTouchEnd(521~610)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
FastClick.prototype.onTouchEnd = function(event) {
        var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;
                //不多说了,你们懂的
        if (!this.trackingClick) {
            return true;
        }
 
        // 还是为了防止多次点击,不过这里多了一个参数cancleNextClick,该属性会在onMouse事件中被判断,为true则彻底禁用事件和冒泡
        if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
            this.cancelNextClick = true;
            return true;
        }
          //识别长按事件,tapTimeOut默认为700ms
        if ((event.timeStamp - this.trackingClickStart) > this.tapTimeout) {
            return true;
        }
 
        // 重置为false避免input事件意外取消
        this.cancelNextClick = false;
                //标记touchEnd时间戳,方便下一次touchStart判定双击
        this.lastClickTime = event.timeStamp;
 
        trackingClickStart = this.trackingClickStart;          //重置这两个参数
        this.trackingClick = false;
        this.trackingClickStart = 0;
 
        //这里又修复了一个ios的bug,啪啪啪一大串英文实在读不懂,解决的是ios6的bug,没兴趣详细了解。。
        if (deviceIsIOSWithBadTarget) {
            touch = event.changedTouches[0];
 
            // In certain cases arguments of elementFromPoint can be negative, so prevent setting targetElement to null
            targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset) || targetElement;
            targetElement.fastClickScrollParent = this.targetElement.fastClickScrollParent;
        }
 
        targetTagName = targetElement.tagName.toLowerCase();
        if (targetTagName === 'label') {//是lable的话激活其指向的组件
                        //findControl这个方法将在后面介绍,大概就是点击label的时候,找到他指向的元素,并获取焦点。
            forElement = this.findControl(targetElement);
                        //如果找到了对应的元素
            if (forElement) {
                this.focus(targetElement);
                if (deviceIsAndroid) {//安卓直接返回
                    return false;
                }
 
                targetElement = forElement;
            }
        else if (this.needsFocus(targetElement)) {//needsFocus方法我将稍后说明,用于判断目标元素是否需要获得焦点
                        //触摸在元素上的事件超过100ms,则置空targetElement并返回false,也就是去走原生的focus方法去了,至于为什么这么做,目前还不是太明白
            // 后面这里又解决了ios5、6上的两个兼容性bug,(⊙o⊙)…不多做研究了,因为这个情况已经太少了。
            if ((event.timeStamp - trackingClickStart) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) {
                this.targetElement = null;
                return false;
            }
                        //获得焦点(这里不是原生的)
            this.focus(targetElement);
                        //sendClick是重点,将在后面讲解,我们需要知道的是这里将立即触发,并没有300ms延迟
            this.sendClick(targetElement, event);
 
            // 这个地方是为了防止ios4、6、7上面select展开的问题
            if (!deviceIsIOS || targetTagName !== 'select') {
                this.targetElement = null;
                event.preventDefault();
            }
 
            return false;
        }
 
        if (deviceIsIOS && !deviceIsIOS4) {
 
            //又是ios的hack代码,貌似是解决滚动区域的点击问题
            scrollParent = targetElement.fastClickScrollParent;
            if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) {
                return true;
            }
        }
 
        //确定目标元素是否需要原生click,方法后面会介绍
        if (!this.needsClick(targetElement)) {
                         //如果这不是一个需要使用原生click的元素,则屏蔽原生事件,避免触发两次click
            event.preventDefault();
                        //触发一次模拟的click事件
            this.sendClick(targetElement, event);
        }
 
        return false;
    };                                 

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FastClick.prototype.findControl = function(labelElement) {
 
    // 支持html5 control属性的话,返回其指向的元素
    if (labelElement.control !== undefined) {
        return labelElement.control;
    }
 
    // 支持html5 htmlFor属性的话,返回其指向的元素
    if (labelElement.htmlFor) {
        return document.getElementById(labelElement.htmlFor);
    }
 
    // 如果以上属性都不支持,尝试返回lable的后代元素
    return labelElement.querySelector('button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea');
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//判断是否需要获得焦点
FastClick.prototype.needsFocus = function(target) {
        switch (target.nodeName.toLowerCase()) {
        case 'textarea':
            return true;
        case 'select':
            return !deviceIsAndroid;
        case 'input':
            switch (target.type) {
            case 'button':
            case 'checkbox':
            case 'file':
            case 'image':
            case 'radio':
            case 'submit':
                return false;
            }
 
            return !target.disabled && !target.readOnly;
        default://目标元素如果有'needsfocus'的类,那么返回true
            return (/\bneedsfocus\b/).test(target.className);
        }
    };

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//一看名字就知道是判断是否需要原生click事件
FastClick.prototype.needsClick = function(target) {
        switch (target.nodeName.toLowerCase()) {
 
        // Don't send a synthetic click to disabled inputs (issue #62)
        case 'button':
        case 'select':
        case 'textarea':
            if (target.disabled) {
                return true;
            }
 
            break;
        case 'input':
 
            //hack代码,ios6浏览器的bug,input[type='file']需要原生click事件
            if ((deviceIsIOS && target.type === 'file') || target.disabled) {
                return true;
            }
 
            break;
        case 'label':
        case 'iframe': // iOS8 homescreen apps can prevent events bubbling into frames
        case 'video':
            return true;
        }
                //这里需要注意了,后面会说明。
        return (/\bneedsclick\b/).test(target.className);
    };       

  onTouchEnd这个方法的代码量比较多一些,因为解决了很多稀奇古怪的兼容性问题,写一个好的js插件还真是不容易,就解决个点击事件300ms延迟问题,hack代码我并没有非常认真的研究到底,也看的晕乎乎的。好了废话不多说,这一部分主要是做了这么几件事情:

  1.首先判断这次touch事件是否还是处于追踪状态,如果不是,那么什么都不做了。

  2.防止多次点击问题

  3.如果是长按事件不予理会  

   4.如果目标元素是lable,那么找到其指向的元素并获取焦点,如果不是,那么判断元素是否需要获取焦点,最后确认目标是否需要原生click事件,如果不需要那么屏蔽掉原生click事件,并触发一次模拟的click事件(tap事件)。

  5.解决了一大推兼容性问题。

4.sendClick

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//合成一个click事件并在指定元素上触发
    FastClick.prototype.sendClick = function(targetElement, event) {
        var clickEvent, touch;
 
        // 在一些安卓机器中,得让页面所存在的 activeElement(聚焦的元素,比如input)失焦,否则合成的click事件将无效
        if (document.activeElement && document.activeElement !== targetElement) {
            document.activeElement.blur();
        }
 
        touch = event.changedTouches[0];
 
        // 合成(自定义事件) 一个 click 事件
        // 通过一个额外属性确保它能被追踪(tracked)
        clickEvent = document.createEvent('MouseEvents');
        clickEvent.initMouseEvent(this.determineEventType(targetElement), truetrue, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, falsefalsefalsefalse, 0, null);
        clickEvent.forwardedTouchEvent = true// fastclick的内部变量,用来识别click事件是原生还是合成的
        targetElement.dispatchEvent(clickEvent); //立即触发其click事件
    };
 
    FastClick.prototype.determineEventType = function(targetElement) {
 
        //安卓设备下 Select 无法通过合成的 click 事件被展开,得改为 mousedown
        if (deviceIsAndroid && targetElement.tagName.toLowerCase() === 'select') {
            return 'mousedown';
        }
 
        return 'click';
    };

  终于走到这一步,这里合成了一个click事件,并且合成的click事件立即触发,是没有300ms的延迟的~~~

  5.onMouse 和 onClick(630~704)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//用于决定是否采用原生click事件
    FastClick.prototype.onMouse = function(event) {
 
        // touch事件一直没触发
        if (!this.targetElement) {
            return true;
        }
 
        if (event.forwardedTouchEvent) { //触发的click事件是合成的
            return true;
        }
        // 确保其没执行过 preventDefault 方法(event.cancelable 不为 true)即可
        if (!event.cancelable) {
            return true;
        }
 
        // 需要做预防穿透处理的元素,或者做了快速(200ms)双击的情况
        if (!this.needsClick(this.targetElement) || this.cancelNextClick) {
            //停止当前默认事件和冒泡
            if (event.stopImmediatePropagation) {
                event.stopImmediatePropagation();
            else {
 
                // 不支持 stopImmediatePropagation 的设备做标记,
                // 确保该事件回调不会执行
                event.propagationStopped = true;
            }
 
            // 取消事件和冒泡
            event.stopPropagation();
            event.preventDefault();
 
            return false;
        }
 
         
        return true;
    };
 
 
    //click事件常规都是touch事件衍生来的,也排在touch后面触发。
    //对于那些我们在touch事件过程没有禁用掉默认事件的event来说,我们还需要在click的捕获阶段进一步
    //做判断决定是否要禁掉点击事件
    FastClick.prototype.onClick = function(event) {
        var permitted;
 
        // 如果还有 trackingClick 存在,可能是某些UI事件阻塞了touchEnd 的执行
        if (this.trackingClick) {
            this.targetElement = null;
            this.trackingClick = false;
            return true;
        }
 
        // 依旧是对 iOS 怪异行为的处理 —— 如果用户点击了iOS模拟器里某个表单中的一个submit元素
        // 或者点击了弹出来的键盘里的“Go”按钮,会触发一个“伪”click事件(target是一个submit-type的input元素)
        if (event.target.type === 'submit' && event.detail === 0) {
            return true;
        }
 
        permitted = this.onMouse(event);
 
        if (!permitted) { //如果点击是被允许的,将this.targetElement置空可以确保onMouse事件里不会阻止默认事件
            this.targetElement = null;
        }
 
        //没有什么意义返回这个
        return permitted;
    };

  常规需要阻断点击事件的操作,在touch 监听事件回调中已经做了处理,这里主要是针对那些 touch 过程(有些设备甚至可能并没有touch事件触发)没有禁用默认事件的 event 做进一步处理,从而决定是否触发原生的 click 事件(如果禁止是在 onMouse 方法里做的处理)。

   总结

  新知识get:

    stopImmediatePropagation与stopPropagation区别:

    1. 他们都可以阻止事件冒泡到父元素

    2. stopImmediatePropagation多做了一件事:比如某个元素绑定多个相同类型事件监听函数,如果执行了stopImmediatePropagation,将按照顺序执行第一个事件监听函数,其余相同类型事件监听函数被阻止。

    zepto“点透”现象被解决是为什么?

    这一点因为我还没有去阅读zepto的源码,所以暂时不能解答。。等待之后再去挖掘。      

 

  第一次阅读源码,感觉很困难,很多东西都不知道,去github上面找问题,但英语太渣,有些看不懂,连蒙带猜加翻译,最终还是求助于百度和谷歌,看到了很多大神的对fastclick的分析文章,感觉自己还有很远的路要走~  

https://blog.csdn.net/handsomexiaominge/article/details/80545902   地址

玩过移动端web开发的同学应该都了解过,移动端上的click事件都会有300毫秒的延迟,这300毫秒主要是浏览器为了判断你当前的点击时单击还是双击,但有时候为了更快的对用户的操作做出更快的响应,越过这个300毫秒的延迟是有点必要的,FastClick做的就是这件事,这篇文章会理清FastClick的整体思路,分析主要的代码,但不会贴出所有的代码,仅分析主干,由于历史原因,FastClick对旧版本的机型做了很多兼容性适配,例如ios4,这部分代码到现在显然已经没有什么分析的意义了,所以贴出的代码会将这部分代码删除。

首先,我们分析一下总体的实现思路,其实FastClick做的事情很简单,首先判断当前浏览器需不需要使用FastClick,例如桌面浏览器,那就不需要,直接绕过,接着,如果需要,则在click事件中拦截事件,取消所有绑定事件的操作,接着用一系列touch事件(touchstart,touchmove,touchend)来模拟click事件,由于touch事件不会延迟,从而达到绕过300毫秒延迟的效果。

先看看FastClick是如何判断浏览器是否需要FastClick的

  1.  
    FastClick.notNeeded = function(layer) {
  2.  
    var metaViewport;
  3.  
    var chromeVersion;
  4.  
    var blackberryVersion;
  5.  
    var firefoxVersion;
  6.  
     
  7.  
    // Devices that don't support touch don't need FastClick
  8.  
    //不支持用于模拟的touchstart事件,无法模拟
  9.  
    if (typeof window.ontouchstart === 'undefined') {
  10.  
    return true;
  11.  
    }
  12.  
     
  13.  
    // 探测chome浏览器
  14.  
    chromeVersion = +(/Chrome\/([0-9]+)/.exec(navigator.userAgent) || [,0])[1];
  15.  
     
  16.  
    if (chromeVersion) {
  17.  
     
  18.  
    //安卓设备
  19.  
    if (deviceIsAndroid) {
  20.  
    metaViewport = document.querySelector('meta[name=viewport]');
  21.  
     
  22.  
    if (metaViewport) {
  23.  
    // 安卓下,带有 user-scalable="no" 的 meta 标签的 chrome 是会自动禁用 300ms 延迟的,无需 FastClick
  24.  
    if (metaViewport.content.indexOf('user-scalable=no') !== -1) {
  25.  
    return true;
  26.  
    }
  27.  
    //chome32以上带有 width=device-width的meta标签的也唔需要使用FastClick
  28.  
    if (chromeVersion > 31 && document.documentElement.scrollWidth <= window.outerWidth) {
  29.  
    return true;
  30.  
    }
  31.  
    }
  32.  
     
  33.  
    // 桌面设备自然无需使用
  34.  
    } else {
  35.  
    return true;
  36.  
    }
  37.  
    }
  38.  
     
  39.  
    //黑莓浏览器,这个。。。了解就好
  40.  
    if (deviceIsBlackBerry10) {
  41.  
    //检测黑莓浏览器
  42.  
    blackberryVersion = navigator.userAgent.match(/Version\/([0-9]*)\.([0-9]*)/);
  43.  
     
  44.  
    // 黑莓10.3以上部分可以不适用FastClick
  45.  
    if (blackberryVersion[1] >= 10 && blackberryVersion[2] >= 3) {
  46.  
    metaViewport = document.querySelector('meta[name=viewport]');
  47.  
     
  48.  
    if (metaViewport) {
  49.  
    // 跟chome一样
  50.  
    if (metaViewport.content.indexOf('user-scalable=no') !== -1) {
  51.  
    return true;
  52.  
    }
  53.  
    // 跟chome一样
  54.  
    if (document.documentElement.scrollWidth <= window.outerWidth) {
  55.  
    return true;
  56.  
    }
  57.  
    }
  58.  
    }
  59.  
    }
  60.  
     
  61.  
    //ie10带有msTouchAction,touchAction相关样式的不需要FastClick
  62.  
    if (layer.style.msTouchAction === 'none' || layer.style.touchAction === 'manipulation') {
  63.  
    return true;
  64.  
    }
  65.  
     
  66.  
    //firefox,跟chome差不多
  67.  
    firefoxVersion = +(/Firefox\/([0-9]+)/.exec(navigator.userAgent) || [,0])[1];
  68.  
     
  69.  
    if (firefoxVersion >= 27) {
  70.  
    // Firefox 27+ does not have tap delay if the content is not zoomable - https://bugzilla.mozilla.org/show_bug.cgi?id=922896
  71.  
     
  72.  
    metaViewport = document.querySelector('meta[name=viewport]');
  73.  
    if (metaViewport && (metaViewport.content.indexOf('user-scalable=no') !== -1 || document.documentElement.scrollWidth <= window.outerWidth)) {
  74.  
    return true;
  75.  
    }
  76.  
    }
  77.  
     
  78.  
    //ie11检测,跟ie10一样,只是ie11废弃了msTouchAction,改为touchAction,依旧是检测样式,检测到相关样式不用FastClick
  79.  
    if (layer.style.touchAction === 'none' || layer.style.touchAction === 'manipulation') {
  80.  
    return true;
  81.  
    }
  82.  
     
  83.  
    //黑名单之外放行,都使用FastClick
  84.  
    return false;
  85.  
    };

长长的一大段,基本上采用黑名单策略,分别检测了chome,黑莓,firefox,ie10,ie11,基本上都是检测对应的meta标签,检测到对应的值的话,弃用FastClick,黑名单之外启用FastClick,仅仅是一个检测函数,看看就好,没什么研究的价值

主体流程,看看FastClick的构造函数,此处仅贴出主要代码,删除了一些兼容的代码

  1.  
    function FastClick(layer, options) {
  2.  
     
  3.  
    //不需要fastClick时直接返回
  4.  
    if (FastClick.notNeeded(layer)) {
  5.  
    return;
  6.  
    }
  7.  
     
  8.  
    //简单的兼容bind方法
  9.  
    function bind(method, context) {
  10.  
    return function() { return method.apply(context, arguments); };
  11.  
    }
  12.  
     
  13.  
     
  14.  
    //注册内部事件
  15.  
    var methods = ['onMouse', 'onClick', 'onTouchStart', 'onTouchMove', 'onTouchEnd', 'onTouchCancel'];
  16.  
    var context = this;
  17.  
    for (var i = 0, l = methods.length; i < l; i++) {
  18.  
    context[methods[i]] = bind(context[methods[i]], context);
  19.  
    }
  20.  
                    //捕获阶段做拦截事件处理
  21.  
    layer.addEventListener('click', this.onClick, true);
  22.  
    layer.addEventListener('touchstart', this.onTouchStart, false);
  23.  
    layer.addEventListener('touchmove', this.onTouchMove, false);
  24.  
    layer.addEventListener('touchend', this.onTouchEnd, false);
  25.  
    layer.addEventListener('touchcancel', this.onTouchCancel, false);
  26.  
     
  27.  
     
  28.  
    //处理通过标签属性绑定事件的方式,转化为通过addEventListener绑定事件,确保fastclick的各种兼容能顺利执行
  29.  
    if (typeof layer.onclick === 'function') {
  30.  
    oldOnClick = layer.onclick;
  31.  
    layer.addEventListener('click', function(event) {
  32.  
    oldOnClick(event);
  33.  
    }, false);
  34.  
    layer.onclick = null;
  35.  
    }
  36.  
    }

FastClick会在执行FastClick.attach操作时被实例化,从代码我们可以看到,做了几件事,检测是否需要使用FastClick,之后注册了一些列的内部方法(onmouse,onclik,ontouchstart等等)并绑定当前作用域,捕获阶段处理onclick事件,冒泡阶段处理touch相关事件并定义相关的内部处理函数,最后对于用标签绑定事件的方式修改为用addEventListener的方式绑定。至于为什么为什么要在捕获阶段处理onclick,我们都知道,现代浏览器对于事件的处理都是先发生捕获,之后再发生冒泡,而为了兼容旧版本浏览器,默认的做法都是将事件绑定在冒泡阶段,在冒泡阶段处理click事件,我们就可以拦截到click事件,并把后续的click绑定操作全都取消掉。

所以,我们大概可以看到,FastClick里面最主要的几个主要方法:onMouse,onClick,onTouchStart,onTouchMoce,onTouchEnd,onTouchMove,onTouchCancel,接下来我们将会逐个分析这些方法

首先,onClick方法

  1.  
    FastClick.prototype.onClick = function(event) {
  2.  
    var permitted;
  3.  
     
  4.  
    // 标记未被取消,直接取消
  5.  
    if (this.trackingClick) {
  6.  
    this.targetElement = null;
  7.  
    this.trackingClick = false;
  8.  
    return true;
  9.  
    }
  10.  
     
  11.  
    //submit控件不做处理
  12.  
    if (event.target.type === 'submit' && event.detail === 0) {
  13.  
    return true;
  14.  
    }
  15.  
     
  16.  
    permitted = this.onMouse(event);
  17.  
     
  18.  
    if (!permitted) {
  19.  
    this.targetElement = null;
  20.  
    }
  21.  
     
  22.  
    return permitted;
  23.  
    };

此处有必要解释一下trackingClick和targetElement这两个标记,trackingClick是一个追踪标志,用touch事件模拟时,正常情况下,开始时(touchstart)会被设置为true,模拟结束(touchend)会被设置为false,而click事件会在touchend事件中被模拟发出,这个后面分析代码的时候我们会看到,很明显,这个时候trackingClick如果检测到为true,是一种不正常的现象,这里FastClick的作者解释为you可能使用了类似的第三方库,导致click事件比FastClick更快的发出,所以此处就不再对结果进行处理,并将内部变量重现修改为默认状态。接着,我们看到,onclick方法其实在内部调用了onmouse方法,事实上主要的操作也都是在onmouse里面执行的,接下来我们看看onMouse

  1.  
    FastClick.prototype.onMouse = function(event) {
  2.  
     
  3.  
    //当前target缺失,有可能模拟触发已经被取消,没有必要阻止 ,直接触发原生事件
  4.  
    if (!this.targetElement) {
  5.  
    return true;
  6.  
    }
  7.  
            //模拟事件标识符
  8.  
    if (event.forwardedTouchEvent) {
  9.  
    return true;
  10.  
    }
  11.  
     
  12.  
    // 事件无法阻止
  13.  
    if (!event.cancelable) {
  14.  
    return true;
  15.  
    }
  16.  
     
  17.  
    //需要fastclick是阻止所有事件触发,快速点击时亦如此
  18.  
    if (!this.needsClick(this.targetElement) || this.cancelNextClick) {
  19.  
     
  20.  
    // Prevent any user-added listeners declared on FastClick element from being fired.
  21.  
    //解除所有后续事件的触发,包括当前节点绑定的其他事件
  22.  
    if (event.stopImmediatePropagation) {
  23.  
    event.stopImmediatePropagation();
  24.  
    } else {
  25.  
     
  26.  
    // Part of the hack for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2)
  27.  
    event.propagationStopped = true;
  28.  
    }
  29.  
     
  30.  
    // 阻止冒泡,阻止默认操作
  31.  
    event.stopPropagation();
  32.  
    event.preventDefault();
  33.  
    return false;
  34.  
    }
  35.  
     
  36.  
    // If the mouse event is permitted, return true for the action to go through.
  37.  
    return true;
  38.  
    };

首先,进入onMouse之后,会通过函数needClick判断当前点击的控件是否需要原生点击的支持,避免出现一些bug,然后判断this.cancelNextClick是否为true,cancelNextClick是用于判断当前操作是否要取消的一个标识符,当两次点击的间隔小于配置的值时,cancelNextClick会被设置为true,这个操作在touchend中进行,稍后会进行分析。当条件满足时,执行阻止事件的操作,具体是执行event.stopImmediatePropagation方法,他能阻止此操作之后绑定在这个节点上的所有其他操作,对于不支持的浏览器,会在event中添加一个propagationStopped的属性,用于兼容操作,这个兼容操作后面再说,接着就是各种阻止冒泡,阻止默认操作,至此,整个阻止操作就完成了,接下来就是如何不延迟300毫秒来触发click事件了,上面说了,用touch事件进行模拟,具体如何,往下走

首先,onTouchStart

  1.  
    FastClick.prototype.onTouchStart = function(event) {
  2.  
    var targetElement, touch, selection;
  3.  
     
  4.  
    //忽略多点触控
  5.  
    if (event.targetTouches.length > 1) {
  6.  
    return true;
  7.  
    }
  8.  
     
  9.  
    targetElement = this.getTargetElementFromEventTarget(event.target);
  10.  
    touch = event.targetTouches[0];
  11.  
     
  12.  
    //记录跟踪状态
  13.  
    this.trackingClick = true;
  14.  
    //记录开始点击时间
  15.  
    this.trackingClickStart = event.timeStamp;
  16.  
    //记录当前处理的节点
  17.  
    this.targetElement = targetElement;
  18.  
     
  19.  
    //记录当前位置
  20.  
    this.touchStartX = touch.pageX;
  21.  
    this.touchStartY = touch.pageY;
  22.  
     
  23.  
    // Prevent phantom clicks on fast double-tap (issue #36)
  24.  
    //阻止双击事件的默认动作
  25.  
    if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
  26.  
    event.preventDefault();
  27.  
    }
  28.  
     
  29.  
    return true;
  30.  
    };

onTouchStart做的事情其实比较少,上面的代码去掉了一些兼容性操作,剩下的只是记录一些基础性的信息,唯一做的事情就是阻止了双击事件的默认操作,如何判断是双击的,event.timeStamp记录了当前点击的时间戳,this.lastClickTime为上一次onTouchEnd时记录的值,记录最后一次点击完成的时间,两者相减小于配置值,则认为是双击,FastClick默认配置的this.tapDelay为200毫秒

接着是onTouchMove

  1.  
    FastClick.prototype.onTouchMove = function(event) {
  2.  
    //没有触发过touchstart事件,直接返回
  3.  
    if (!this.trackingClick) {
  4.  
    return true;
  5.  
    }
  6.  
     
  7.  
    // If the touch has moved, cancel the click tracking
  8.  
    //判断当前是否移动,移动过则取消跟踪事件
  9.  
    if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) {
  10.  
    this.trackingClick = false;
  11.  
    this.targetElement = null;
  12.  
    }
  13.  
     
  14.  
    return true;
  15.  
    };

操作也是比较简单,trackingClick是一个跟踪字段,在onTouchStart中设置为true,如此处发现不为true,则发生了错误,直接会返回,接着就是判断当前是否有移动,主要就是获取当前手指的位置跟触发控件的位置进行比较,具体方法由于篇幅关系就不解释了,本篇博文仅解释主干内容,当触摸点移动了,则将trackingClcik和targetElement恢复为默认,之后在touchEnd中就不会发出模拟事件触发click

接着对于特殊原因取消的情况,绑定了touchcancel事件

  1.  
    FastClick.prototype.onTouchCancel = function() {
  2.  
    this.trackingClick = false;
  3.  
    this.targetElement = null;
  4.  
    };

这个并没有什么特别的地方,特殊情况发生了,如手指戳下的时候突然来电话了各种情况导致触摸中断,则将所有跟踪变量恢复到初始状态。

最关键的onTouchEnd

  1.  
    FastClick.prototype.onTouchEnd = function(event) {
  2.  
    var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;
  3.  
                    //触摸点移动或者其他操作导致取消
  4.  
    if (!this.trackingClick) {
  5.  
    return true;
  6.  
    }
  7.  
     
  8.  
    //不处理快速点击
  9.  
    if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
  10.  
    this.cancelNextClick = true;
  11.  
    return true;
  12.  
    }
  13.  
     
  14.  
    //不处理长按
  15.  
    if ((event.timeStamp - this.trackingClickStart) > this.tapTimeout) {
  16.  
    return true;
  17.  
    }
  18.  
     
  19.  
    // 将所有的跟踪变量设置为初始状态,供下次点击使用
  20.  
    this.cancelNextClick = false;
  21.  
     
  22.  
    this.lastClickTime = event.timeStamp;
  23.  
     
  24.  
    trackingClickStart = this.trackingClickStart;
  25.  
    this.trackingClick = false;
  26.  
    this.trackingClickStart = 0;
  27.  
     
  28.  
    targetTagName = targetElement.tagName.toLowerCase();
  29.  
    //处理组件为label时的状况,获取label对应绑定的控件
  30.  
    if (targetTagName === 'label') {
  31.  
    forElement = this.findControl(targetElement);
  32.  
    if (forElement) {
  33.  
    this.focus(targetElement);
  34.  
    if (deviceIsAndroid) {
  35.  
    return false;
  36.  
    }
  37.  
     
  38.  
    targetElement = forElement;
  39.  
    }
  40.  
    } else if (this.needsFocus(targetElement)) {
  41.  
     
  42.  
    //第一个判断作者认为如果按下的时间超过了100毫秒,此时已经没有必要再执行模拟操作了,按原生的click执行操作即可,第二个判断则是处理ios相关的一个bug
  43.  
    if ((event.timeStamp - trackingClickStart) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) {
  44.  
    this.targetElement = null;
  45.  
    return false;
  46.  
    }
  47.  
     
  48.  
    this.focus(targetElement);
  49.  
    this.sendClick(targetElement, event);
  50.  
     
  51.  
    return false;
  52.  
    }
  53.  
     
  54.  
    //不需要原生点击时,触发模拟click事件
  55.  
    if (!this.needsClick(targetElement)) {
  56.  
    event.preventDefault();
  57.  
    this.sendClick(targetElement, event);
  58.  
    }
  59.  
     
  60.  
    return false;
  61.  
    };

此处,ontouchEnd,首先忽略快速点击和长按,然后恢复所有的初始化变量,之后会判断当前控件是不是label,是的话利用findControl函数找到label关联的组件,并赋值给当前的targetElement 统一处理,具体杂七杂八的函数会在后面再解释,接着会判断当前组件触发click时需不需要获取焦点,如果需要,则获取焦点后,触发模拟事件,此处关注两个函数focus和sendClick,focus函数帮助当前target获取焦点,sendClick则发送模拟事件,focus函数关键代码如下

  1.  
    /**
  2.  
    * 兼容写法,获取焦点,光标放置到末尾
  3.  
    */
  4.  
    FastClick.prototype.focus = function(targetElement) {
  5.  
    var length;
  6.  
    if (deviceIsIOS && targetElement.setSelectionRange && targetElement.type.indexOf('date') !== 0 && targetElement.type !== 'time' && targetElement.type !== 'month') {
  7.  
    length = targetElement.value.length;
  8.  
    targetElement.setSelectionRange(length, length);
  9.  
    } else {
  10.  
    targetElement.focus();
  11.  
    }
  12.  
    };

此处,对于ios浏览器,采用兼容的写法,用setSelectionRange来获取焦点,setSelectionRange可以用来选取输入框的值,此处将选取的开始和结束都设置为value的length,则可以把光标放到组件的末尾并且获得焦点

接下来是sendClick,这也是整个fastclick的关键,用于模拟事件的发生,主要实现如下:

  1.  
    FastClick.prototype.sendClick = function(targetElement, event) {
  2.  
    var clickEvent, touch;
  3.  
     
  4.  
    //兼容操作,部分安卓机当前焦点所在的节点如果不是模拟节点,需要把焦点去除,否则影响效果
  5.  
    if (document.activeElement && document.activeElement !== targetElement) {
  6.  
    document.activeElement.blur();
  7.  
    }
  8.  
     
  9.  
    touch = event.changedTouches[0];
  10.  
     
  11.  
    // Synthesise a click event, with an extra attribute so it can be tracked
  12.  
    clickEvent = document.createEvent('MouseEvents');
  13.  
    clickEvent.initMouseEvent(this.determineEventType(targetElement), true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
  14.  
    clickEvent.forwardedTouchEvent = true;
  15.  
    targetElement.dispatchEvent(clickEvent);
  16.  
    };
实现代码很简单,就是就是创建一个event对象,然后触发它,注意,这个地方用到了initMouseEvent来初始化event对象,但目前initMouseEvent已经从web删除了,换句话说它已经不是标准方法了,未来的浏览器可能不会再继续提供支持,所以自己尽量不要使用这个特性,可以用MouseEvent这个特定的事件构造器来替代它,详细使用方法可以参考戳我带你飞
 

至此,我们的所有主流程已经讲完了,接下来我们说一下里面涉及到的一些杂七杂八的函数

首先,如何兼容event.stopImmediatePropagation,上面我们说了,这个函数可以解除当前绑定操作之后的所有绑定到此节点上的操作,但存在部分浏览器不兼容,对于一些不兼容的浏览器,上面说到绑定事件fastclick会手动给event对象添加一个propagationStopped属性,那这个属性有什么用呢,我们看看下面的代码

  1.  
    layer.addEventListener = function(type, callback, capture) {
  2.  
    var adv = Node.prototype.addEventListener;
  3.  
    if (type === 'click') {
  4.  
    adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
  5.  
    //通过对event对象添加属性来控制事件的触发
  6.  
    if (!event.propagationStopped) {
  7.  
    callback(event);
  8.  
    }
  9.  
    }), capture);
  10.  
    } else {
  11.  
    adv.call(layer, type, callback, capture);
  12.  
    }
  13.  
    };

这段函数出现在fastclick的构造函数中,为了主干代码的清晰,在上面我把它删掉了,对于不兼容event.stopImmediatePropagation的浏览器,它重写了addEventListener方法,增加了对stopImmediatePropagation属性的判断,这样当上面的propagationStopped被设置为true的时候,后续的绑定操作就都不会继续进行了。

接下来一个方法是获取label关联控件的方法,findControl

  1.  
    FastClick.prototype.findControl = function(labelElement) {
  2.  
     
  3.  
    //通过control属性获取
  4.  
    if (labelElement.control !== undefined) {
  5.  
    return labelElement.control;
  6.  
    }
  7.  
     
  8.  
    //通过获取for属性
  9.  
    if (labelElement.htmlFor) {
  10.  
    return document.getElementById(labelElement.htmlFor);
  11.  
    }
  12.  
     
  13.  
    //如各种不兼容,则获取label标签中的第一个
  14.  
    return labelElement.querySelector('button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea');
  15.  
    };

首先,findControl会通过html5的control属性来获取label包含的表单元素,如果失败,转而获取label的for属性对应的表单元素,因为for属性也是html5的,旧浏览器可能不兼容,最后如果获取不了,则会获取label元素的子元素中的第一个表单元素,进而来获取label对应的表单元素。

嗯,啰啰嗦嗦大概说完了,如有说错的地方,欢迎评论区指出

fastclick源码分析的更多相关文章

  1. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  2. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  3. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  4. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  5. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  6. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  7. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  8. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

  9. ABP源码分析三:ABP Module

    Abp是一种基于模块化设计的思想构建的.开发人员可以将自定义的功能以模块(module)的形式集成到ABP中.具体的功能都可以设计成一个单独的Module.Abp底层框架提供便捷的方法集成每个Modu ...

随机推荐

  1. 20169214 2016-2017-2 《移动平台开发实践》Android程序设计 实验报告

    实验四 Android程序设计 课堂练习 实验题目 采用后缀表达式法,设计一个建议计算器,实现+.-.*./四种运算. 代码实现 码云链接 关键代码部分及结果如下: Android程序实验 Andro ...

  2. python之文件操作总结

    目录 文件:数据的抽象和集合 文件的打开关闭 文件内容的读取 文件的全文本操作 文件的逐行操作 数据文件的写入 使用json模块 文件:数据的抽象和集合 文件是存储在辅助存储器上的数据序列 文件是数据 ...

  3. wp8.1 app退出操作提示

    微软的wp8.1 sdk相比之前wp8 sdk以及相关dll类库,微软又重新编译过,相关系统类库也经过精简,删改了部分传统dll库中的方法对象,很多常用方法对象被写进Windows.UI为前缀的命名空 ...

  4. C#中IO操作

    using sysytem.Io; File.Exists() 检查文件是否存在, Directory.Exists() 检查文件夹是否存在 FileInfo DirectoryInfo 可实例化 对 ...

  5. 菜鸟的Xamarin.Forms前行之路——原生Toast的简单实现方法

    项目中信息提示框,貌似只有个DisplayAlert,信息提示太过于单一,且在有些场合Toast更加实用,以下是一个简单的原生Toast的实现方法 项目地址:https://github.com/we ...

  6. performance checklist

    - embree integration                                       0w - uncompressed bvh nodes               ...

  7. [转]解读Unity中的CG编写Shader系列8——多光源漫反射

    前文中完成最简单的漫反射shader只是单个光源下的漫反射,而往往场景中不仅仅只有一个光源,那么多个光源的情况下我们的物体表面的漫反射强度如何叠加在一起呢?前文打的tag "LightMod ...

  8. 洛谷P5265 【模板】多项式反三角函数

    题面 传送门 题解 我数学好像学得太差了 据说根据反三角函数求导公式 \[{d\over dx}\arcsin x={1\over \sqrt{1-x^2}}\] \[{d\over dx}\arct ...

  9. Zabbix监控详解

    Zabbix是什么 Zabbix 是由Alexei Vladishev创建,目前由Zabbix SIA在持续开发和支持. Zabbix 是一个企业级的分布式开源监控方案. Zabbix是一款能够监控各 ...

  10. ExclusiveTouch

    Setting this property to true causes the receiver to block the delivery of touch events to other vie ...