说谁你只是"会用"jQuery?

栏目: Node.js · 发布时间: 8年前

内容简介:说谁你只是"会用"jQuery?

前言

套用上篇文章 向zepto.js学习如何手动触发DOM事件 的开头:grinning::grinning::grinning:

前端在最近几年实在火爆异常,vue、react、angular各路框架层出不穷,咱们要是不知道个双向数据绑定,不晓得啥是虚拟DOM,也许就被鄙视了。火热的背后往往也是无尽的浮躁,学习这些先进流行的类库或者框架可以让我们走的更快,但是静下心来回归基础,把基石打牢固,却可以让我们走的更稳,更远。

最近一直在看zepto的源码,希望通过学习它掌握一些框架设计的技巧,也将很久不再拾起的js基础重新温习巩固一遍。如果你对这个系列感兴趣,欢迎 点击 watch ,随时关注动态。这篇文章主要想说一下zepto中事件模块(event.js)的添加事件 on 以及移除事件 off 实现原理。

原文地址

仓库地址

说在前面

在没有vue和react,甚至angular都没怎么接触的刀耕火种的时代,jQuery或者zepto是我们手中的利器,是刀刃,他让我们游刃有余地开发出兼容性好的漂亮的网页,我们膜拜并感叹作者带来的便利,沉浸其中,无法自拔。

=== 准备放一张照片一梭子jQuery

但是用了这么久的zepto你知道这样写代码

$('.list').on('click', 'li', function(e){
  console.log($(this).html())
})

是怎么实现事件委托的吗?为啥此时的 this 就是你点中的 li 呢?

平常我们可能还会这样写。

$('.list li').bind('click', function(){})

$('.list').delegate('li', 'click', function(){})

$('.list li').live('click', function(){})

$('.list li').click(function(){})

写法有点多,也许你还有其他的写法,那么

on

bind

delegate

live

click()

这些添加事件的形式,有什么区别,内部之间又有什么联系呢?

相信你在面试过程中也遇到过类似的问题( 看完这边文章,你可以知道答案的噢:hushed: )?

接下来我们从源码的角度一步步去探究其内部实现的原理。

一切从 on 开始

为什么选择从 on 添加事件的方式开始说起,原因在于其他写法几乎都是 on 衍生出来的,明白了 on 的实现原理,其他的也就差不多那么回事了。

祭出一张画了好久的图

说谁你只是

上面大概是zepto中 on 形式注册事件的大致流程,好啦开始看源码啦,首先是on函数,它主要做的事情是注册事件前的参数处理,真正添加事件是内部函数add。

$.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 (callback === undefined || 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内部函数真正去给选中的元素注册事件

    add(element, event, callback, data, selector, delegator || autoRemove)
  })
}

直接看到这么一大坨的代码不易于理解,我们分段进行阅读。

第一段

var autoRemove, delegator, $this = this
  if (event && !isString(event)) {
    $.each(event, function(type, fn){
      $this.on(type, selector, data, fn, one)
    })
    return $this
  }

这段代码主要是为了处理下面这种调用形式。

$('.list li').on({
  click: function(){
    console.log($(this).html())
  },
  mouseover: function(){
    $(this).css('backgroundColor', 'red')
  },
  mouseout: function(){
    $(this).css('backgroundColor', 'green')
  }
})

这种写法我们平时写的比较少一点,但是确实是支持的。而zepto的处理方式则是循环调用 on 方法,以 key 为事件名, val 为事件处理函数。

在开始第二段代码阅读前,我们先回顾一下,平时经常使用 on 来注册事件的写法一般有哪些

// 这种我们使用的也许最多了
on(type, function(e){ ... })

// 可以预先添加数据data,然后在回调函数中使用e.data来使用添加的数据
on(type, data, function(e){ ... })

// 事件代理形式
on(type, [selector], function(e){ ... })

// 当然事件代理的形式也可以预先添加data
on(type, [selector], data, function(e){ ... })

// 当然也可以只让事件只有一次起效

on(type, [selector], data, function(e){ ... }, true)

还会有其他的写法,但是常见的可能就是这些,第二段代码就是处理这些参数以让后续的事件正确添加。

第二段

// selector不是字符串形式,callback也不是函数
if (!isString(selector) && !isFunction(callback) && callback !== false)
    callback = data, data = selector, selector = undefined
    // 处理data没有传或者传了函数
  if (callback === undefined || data === false)
    callback = data, data = undefined
    // callback可以传false值,将其转换为returnFalse函数
  if (callback === false) callback = returnFalse

三个if语句很好的处理了多种使用情况的参数处理。也许直接看不能知晓到底是如何做到的,可以试试每种使用情况都代入其中,找寻其是如何兼容的。

接下来我们第三段

这段函数做了非常重要的两件事

  1. 处理one传入为true,事件只触发一次的场景
  2. 处理传入了selector,进行事件代理处理函数开发

我们一件件看它如何实现。

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

内部用了一个 remove 函数,这里先不做解析,只要知道他就是移除事件的函数就可以,当移除事件的时候,再执行了传进来的回调函数。进而实现只调用一次的效果。

那么事件代理又是怎么实现咧?

回想一下平常自己是怎么写事件代理的,一般是利用事件冒泡(当然也可以使用事件捕获)的性质,将子元素的事件委托到祖先元素身上,不仅可以实现事件的动态性,还可以减少事件总数,提高性能。

举个例子

我们把原本要添加到li上的事件委托到父元素ul上。

<ulclass="list">
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>
let $list = document.querySelector('.list')

$list.addEventListener('click', function(e){
  e = e || window.event
  let target = e.target || e.srcElement
  if (target.tagName.toLowerCase() === 'li') {
    target.style.background = 'red'
  }
}, false)

点击查看效果


以上所述就是小编给大家介绍的《说谁你只是"会用"jQuery?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First Web Design

Head First Web Design

Ethan Watrall、Jeff Siarto / O’Reilly Media, Inc. / 2009-01-02 / USD 49.99

Want to know how to make your pages look beautiful, communicate your message effectively, guide visitors through your website with ease, and get everything approved by the accessibility and usability ......一起来看看 《Head First Web Design》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具