作为一个专业的前端开发,你真的会用 jQuery 吗?jQuery 的精妙,随着我对 JavaScript 的理解的加深而愈加感到惊讶

其实,如果我们不需要进行对 DOM 的操作兼容(主要针对 IE),那么大抵可以完全抛弃 jQuery,毕竟 ES5 和 DOM-Lv2 已经提供了很多 jQuery 常用的功能了,既然使用 jQuery 是为了开发效率和兼容性考量,那么我是很推荐尽量使用 jQuery 提供的大部分功能,毕竟这样对代码阅读者和书写者都是很舒服的事情,使用 $.each() 代替了常规的 for 循环,使用 $.isEmptyObject(object) 来判断空对象,等等

很多人觉得使用 jQuery 不够高端,甚至觉得它会带来性能问题,但其实我认为,对于快速迭代的考虑,代码的可读性和兼容性优先级要远高于那点可怜的性能加成(事实上,自己造的轮子真不一定比 jQuery 性能好,简单的例子,jQuery 通常会考虑缓存 DOM,同时不会忘记卸载过期事件)

本文档基于 jQuery API 中文文档 总结

关于jQuery的最佳实践

可参考 阮一峰 的整理 jQuery最佳实践,关键点如下:

  1. 使用最新版本的 jQuery
  2. 用对选择器(尽量用 ID)
  3. 理解子元素和父元素的关系
  4. 不要过度使用 jQuery
  5. 做好缓存
  6. 使用链式写法
  7. 事件的委托处理
  8. 少改动 DOM 结构
  9. 正确处理循环
  10. 尽量少生成 jQuery 对象
  11. 选择作用域链最短的方法
  12. 使用 Pub / Sub 模式管理事件(观察者模式)

选择器

jQuery 实现的选择器方法很多已经大放异彩,堪称业界典范,同时这个抽出来单独作为一个标准——Sizzle 选择器引擎

避免使用的选择器

以下的选择器均有或多或少的性能问题,主要原因是无法充分利用原生 DOM 提供的 querySelectorAll() 方法

  • $('*')
  • $(':eq')
  • $(':gt')
  • $(':lt')
  • $(':not')
  • $(':has')
  • $(':file') (建议替换成 $('selector[type = "file"]')
  • $(':checkbox') (建议替换成 $('selector[type = "checkbox"]')
  • $(':image') (建议替换成 $('selector[type = "image"]' )
  • $(':password') (建议替换成 $('selector[type = "password"]')
  • $(':radio') (建议替换成 $('selector[type = 'radio']')
  • $(':reset') (建议替换成 $('selector[type = 'reset']')
  • $('[attribute != "value"]') (建议替换成 $('class')

尽量先使用纯CSS选择器,再使用 .filter('selector') 代替的选择器

  • $(':hidden')$(':visible')

    visibility: hiddenopacity: 0 在 jQuery 中被认为是可见的,因为他们仍然占据布局空间
    相对地,不可见主要根据以下四点判断(jQuery 3.0 对该标准有所修改)

    • 他们的CSS display 值是 none
    • 他们是 type="hidden" 的表单元素
    • 它们的宽度和高度都显式设置为 0
    • 一个祖先元素是隐藏的,因此该元素是不会在页面上显示
  • $(':animated')
  • $(':even')$(':odd')(注意与 $(':nth-child(even)')$(':nth-child(odd)') 的区别)
  • $(':first')$(':last')
  • $(':header')
  • $(':empty')$(':parent') 涉及到的子元素包括文本节点
  • 表单元素:$(':button')$(':input')$(':selected')$(':submit')$(':text')

比较特殊的选择器

  • $(':target')
  • $(':lang')
  • $(':root') (建议换用 $('html') 替换,提高可读性)
  • $(':contains(text)') 查找的一个文本字符串,区分大小写
  • $(':nth-child(n)') 索引从1开始
  • $(':focus') 如果正在寻找当前的焦点元素,可使用 $(document.activeElement) 检索,而不必查找整个 DOM 树
  • $(':disabled') 检查元素的 disabled 属性的布尔值,$(['type = "disabled"]') 检查是否存在的 disabled 属性

CSS和属性

用法区别

  • .prop() 方法 方法返回 property 的布尔值,.attr() 方法返回 attributes 的字面量值,区别使用
  • .prop().attr().val().css() 等方法只获取或设置第一个匹配元素的属性值 。如果元素上没有该属性,或者如果没有匹配的元素。那么该方法会返回 undefined
  • .css('height').height() 之间的区别是后者返回一个没有单位的数值(例如,400),前者是返回带有完整单位的字符串(例如,400px)
  • 尺寸计算方法间的区别
    height
    innderHeight
    outerHeight
    width
    innerWidth
    outerWidth

  • 计算 windowdocument 的高度

    1
    2
    3
    4
    // 获取浏览器视口宽高 viewport
    $(window).height();
    // 获取文档宽高 document
    $(document).height();
  • .offset() 方法检索一个元素相对于文档 document 的当前位置。和 .position() 的差别是:.position() 是相对于相对于父级元素的位移

  • .offsetParent() 取得离指定元素最近的含有定位信息的祖先元素

注意事项

  • 当设置样式名 ("class") 属性时,必须使用引号,因为 class 是 JavaScript 标识符,为保持风格统一和安全,建议 API 调用传入类似对象的属性名均使用引号,如下

    1
    2
    3
    4
    $('body').attr({
    'alt': 'Beijing Brush Seller',
    'class': 'photo'
    });
  • .prop().attr()

    Properties 属性一般影响 DOM 元素的状态并不会改变序列化的 HTML attribute 属性。例如,input 元素的 value 属性,inputbutton 元素的 disabled 属性,以及 checkboxchecked 属性。应该使用 .prop() 方法设置 disabledchecked 属性,而不是使用 .attr() 方法

    .val() 方法应该用于存取 value 值。还要注意的是 .removeProp() 方法不应该被用来设置这些属性为 false。一旦原生的属性被移除,就无法再被添加

    IE8 及以下版本,不要使用 .prop() 对非基本类型(numberstringboolean)的属性赋值,为了安全请使用 .data() 方法

    尺寸相关的 API 返回的数字, 包括的 .height(), 在某些情况下可能带有小数。你的代码不应该假定它是一个整数。 另外,当页面被用户缩放时,返回的尺寸可能是不正确的;浏览器没有一个公开的 API 来检测这种情况。

    当元素或其父元素被隐藏时,.height() 得到的值不能保证准确。要得到准确的值,你应该确保该元素在使用 .height() 前可见。jQuery 将尝试临时显示,然后再隐藏元素来测量元素尺寸,但这是不可靠的,(即使得到准确的值)也会显著影响页面的性能。这总临时显示然后再隐藏的测量功能,可能在 jQuery 未来的版本中删除。

实用技巧

  • 通过函数回调的方法动态获取 / 修改属性等

    1
    2
    3
    4
    5
    6
    7
    8
    // 适用于.prop(), .attr(), .val(), .addClass(), .removeClass(), .css(), toggleClass()
    $('div').attr('title', function (index, val) {
    return index + val; // index 为索引,val为属性title的值
    });
    //实际应用
    $('img').attr('src', function () {
    return '/resources/' + this.title; // title为图片名,动态修改其相对路径
    });
  • .val() 方法

    1
    2
    3
    4
    5
    6
    7
    // 通过 .val() 方法从 textarea 元素中取得的值是不含有回车(\r)字符的。
    // 但是如果该值是通过 XHR 传递给服务器的,回车(\r)字符会被保留(或者是被浏览器添加的,但是在原始数据中并不包含回车(\r))。
    $.valHooks.textarea = { // 原始数据中保留回车(\r)字符
    get: function (elem) {
    return elem.value.replace( /\r?\n/g, "\r\n" );
    }
    };

特殊

  • $.cssHooks 直接向 jQuery 中添加钩子,用于覆盖设置或获取特定 CSS 属性时的方法,目的是为了标准化 *CSS 属性名或创建自定义属性
  • .toggleClass() 可以传入 Boolean 值或回调函数来灵活调整切换

避免使用

  • $.data( element, key, value ) 存储任意数据到指定的元素,返回设置的值。 允许我们在DOM元素上绑定任意类型的数据,避免了循环引用的内存泄漏风险, 这是一个底层的方法,你应该用 .data() 代替

操作

用法区别

  • .prepend('将要插入的内容').prependTo('位置') 均为 内部 插入后面, .append('将要插入的内容').appendTo('位置') 均为 内部 插入前面
  • .after('将要插入的内容').insertAfter('位置') 均为 外部 插入后面,.before('将要插入的内容').insertBefore('位置') 均为 外部 插入前面
  • .empty() 移除节点内容(不含节点)并解除数据 / 事件关联,.remove() 移除节点内容(含节点)并解除数据 / 事件关联,.detach() 移除节点内容(含节点)但不解除数据 / 事件关联(再插回来还能用)
  • .unwrap() 删除元素的父级元素。和 .wrap() 的相反
  • .replaceAll('位置').replaceWith('内容') 功能类似,目标和源相反,用来替换的元素从老地方移到新位置,而不是复制,并且会删除与节点相关联的所有数据和事件处理程序

特殊

  • .clone([withDataAndEvents] [, deepWithDataAndEvents]) 深度复制所有匹配的元素集合,包括所有匹配元素、匹配元素的下级元素、文字节点

注意事项

  • 使用 .html() 时元素中的任何内容会完全被新的内容取代。新的内容替换前,jQuery 从子元素删除其他结构,如数据和事件处理程序(这样可以防止内存溢出)

    IE 中(包括 IE9),设置 HTML 元素的文本内容可能会破坏其子节点的文本节点,结果导致子节点的文本节点从文档中被删除。如果你想保留这些 DOM 元素的引用,需要他们将保持不变,请使用 .empty() 来代替 .html(string)以便从文档中删除元素之前的元素被分配到新的字符串

  • 要设置一个 <script> 元素的内容, 其不包含 HTML, 使用 .text() 方法而不是 .html().text() 方法不能使用在 input 元素或 scripts 元素上。inputtextarea 需要使用 .val() 方法获取或设置文本值。得到 scripts 元素的值,使用 .html() 方法

遍历

用法区别

  • .filter(function(index)) 通过函数来进行高级筛选

    1
    2
    3
    $('li').filter(function (index) { //this是当前DOM元素,index为索引
    return $('strong', this).length == 1;
    }).css('background-color', 'red');
  • .map(callback(index, domElement)) 方法特别适用于获取或设置元素集合中的值

  • .slice(start [, end ]) 根据指定的下标范围,过滤匹配的元素集合,并生成一个新的 jQuery 对象
  • add() 添加新 jQuery 对象到堆栈,而 addback() 添加堆栈中元素集合到当前集合
  • .contents().children() 检索直接子元素,.find() 检索后代元素,其中 .contents() 包括文本节点和注释节点,以及 jQuery 对象中产生的 HTML 元素
  • .parent() 匹配直接父元素, .parents() 匹配祖先元素(不包括自己,可能返回元素数组), .parentsUntil() 查找并返回遇到跟参数匹配的元素(不包括)前的所有祖先元素, .closest() 匹配祖先元素(包括自己,只返回最近一个)

    .next().nextAll().nextUntil().prev().prevAll().prevUntil() 两组用法类似

    $("html").parent() 方法返回一个包含 document 的集合,而 $("html").parents() 返回一个空集合

其他

  • .each(function(index, Element)) 方法用来让 DOM 循环结构更简单更不易出错。

    1
    2
    3
    4
    5
    6
    $('li').each(function (index) {
    console.log(index + ': ' + $(this).text()); // this 总是指向对应的 DOM 元素,使用 $(this) 获得的是 jQuery 对象
    if (index === 3) {
    return false; // 使用return提前结束each()
    }
    });
  • end() 可用于链式写法 终止在当前链的最新过滤操作,并返回匹配的元素的以前状态

    1
    2
    3
    4
    $('ul.first').find('.foo')
    .css('background-color', 'red')
    .end().find('.bar') // 返回到 $('ul.first') 的栈
    .css('background-color', 'green')

效果

基础动画

  • 所有jQuery效果都能通过设置 jQuery.fx.off = true 全局地关闭,效果等同于持续时间设置为 0
  • 元素的 display 原本属性值保存在 jQuery 的数据缓存,这样使用切换显示的方法时,将恢复其原本 display
  • 可以传入一个回调在对应动画完成后运行,比如在 弹窗中,淡出后删除其对应 DOM 结构
  • .fadeTo() 方法调整匹配元素的透明度

自定义动画

  • 建议避免使用复杂的 jQuery 动画,使用 CSS3 动画以在高级浏览器中获得更好性能与表现

事件

jQuery 的事件绑定子系统为每一个事件处理函数分配一个唯一的 ID 用于对其进行跟踪,这样的话,当需要解除绑定特定的事件处理时,系统就知道该解除绑定哪个事件处理函数

常见事件类型及用法

  • $(document).ready(handler)$(handler) 等价,前者更语义化

    1
    2
    3
    jQuery(document).ready(function ($) {
    // 当引入其他类库时,在此可正常使用 $
    });
  • .ready() 方法通常和 <body onload="handler"> 属性是不兼容的。如果 load 必须使用,要么不使用 .ready(),要么使用 jQuery 的 .load() 方法向 window 或一些指定的元素(例如,图片)绑定 load 事件

  • 为代码统一及便于记忆,建议事件绑定均使用通用方法而不是速写方式,首选方法 .on().off().trigger()
    • 浏览器事件:.resize().scroll()
    • 表单事件:.blur().change().focus().select().submit()
    • 键盘事件:.keydown().keypress().keyup()
    • 鼠标事件:.click().contextmenu().dbclick().focusin().foucsout().hover().mouseenter().mouseleave().mousedown().mouseup().mousemove()mouseout()
  • resize 事件处理中的代码,不应该依赖于事件被调用的次数。由于不同浏览器对该事件实现的方式不同,该事件被调用的时机也不同
  • 一个元素的值改变的时候将触发 change 事件。此事件仅限用于 <input><textarea><select> 元素

    注意:使用 JavaScript 改变输入元素的值,例如使用 .val(),将不会触发该事件。

  • 在一个元素中进行文本选择时,select 事件就会被触发。此事件只用在 <input type="text"><textarea>

  • 按下并按住这个键(不松开)的时,keydown 事件只触发一次,但是 keypress 会在每个字符插入的时候都会触发。组合键(如 Shift )会触发 keydown 事件,但不会触发 keypress 事件。

    值得注意的是 keydownkeyup 提供一个代码,表示哪一个键被按下,而 keypress 表示被输入哪个字符。

    例如,按下了小写的 a,在 keydownkeyup 中,对应该键的代码是 65,但是对于 keypress 而言,接收到的代码是 97。如果是大写 A 的话,则所有的相关事件接收到的代码都是 65

    由于这个区别,若想捕获敲击了哪个特殊键的话,例如方向键,使用 .keydown().keyup() 更好。如果要捕获实际输入文本,.keypress() 可能是一个更好的选择。

  • focusin(或 focusout)事件会在元素(或者其内部的任何元素)获得焦点时触发。这跟 focus(或blur)事件的显著区别在于,它可以在父元素上检测子元素获得焦点的情况(换而言之,它支持事件冒泡)

  • .hover() 方法同时绑定 mouseentermouseleave 事件

    $(selector).hover(fnIn, fnOut) 是 $(selector).mouseenter(fnIn).mouseleave(fnOut) 的简写

  • mouseleavemouseoutmouseentermouseover 之间的不同之处是事件的冒泡的方式, 一般用前者

    mouseleave 事件是 IE 专有的, 由于该事件在平时很有用, jQuery 模拟了这一事件,使它可用于所有浏览器

事件绑定

  • 使用 .on() 方法带代替 .delegate() (多元素绑定)和 .bind()(单元素绑定)方法将事件处理程序绑定到 document
  • one() 方法绑定一次性事件, 等价于 .on() 触发事件后马上 off()
  • .trigger() 模拟事件的触发,具备合成的 event 对象。如不希望触发默认事件或冒泡,使用 .triggerHandler()

事件对象

  • event.currentTarget 总是等于该函数的 this
  • event.data 作为一个数据对象传递给一个事件
  • event.preventDefault() 阻止默认事件行为
  • event.isDefaultPrevented()event.isImmediatePropagationStopped()event.isPropagationStopped()均为判断对应方法是否调用过,返回布尔值
  • event.pageXevent.pageY 为鼠标相对于 document 的左边缘的位置(左/右)
  • event.stopImmediatePropagation() 阻止 剩余 的事件处理函数执行并且防止事件冒泡到 DOM 树; 而event.stopPropagation() 不会阻止同一元素上的其它事件,同样防止事件冒泡到 DOM 树上(不触发任何前辈元素上的事件)
  • event.target 是注册事件时的元素(或子元素), 通过比较它与 this 来确定事件是否由冒泡触发。观察event.relatedTarget 找到事件涉及的其他 DOM 元素

    event.relatedTarget 对于 mouseout 事件,指向被进入的元素; 对于 mouseover 事件,指向被离开的元素

  • 事件处理函数的最后返回值会被记录在 event.result

  • event.timeStamp 记录事件的时间戳

    1
    2
    3
    4
    5
    6
    7
    var last
    $('div').click(function(event) {
    if (last) {
    console.log(event.timeStamp - last); //打印两次点击时间差
    }
    last = event.timeStamp;
    });
  • event.whichevent.keyCodeevent.charCode 标准化了。用来监视鼠标和键盘输入

特殊

  • $.holdReady() 方法允许调用者延迟 jQuery 的 ready 事件(在事件触发前)

    为了延迟 ready 事件,首先要调用 $.holdReady(true),当 ready 事件准备执行时,再调用 $.holdReady(false)

  • 命名空间 event.namespace 的用法
    1
    2
    3
    4
    5
    6
    $('p').bind('test.something', function (event) { // 绑定一个自定义事件
    alert(event.namespace); // event指向test
    });
    $("button").click(function(event) { // 通过浏览器事件来间接触发自定义命名事件
    $('p').trigger('test.something');
    });