Getting Started
Including jQuery
Include jQuery via a CDN for fast, cached loading. The minified version (.min.js) is for production; the uncompressed version is for debugging. Always provide a local fallback in case the CDN is unreachable. With npm, you can import jQuery as a module — useful for bundlers like Webpack/Vite. jQuery 3.7.x is the current line; older IE is no longer supported.
<!-- Production (CDN, minified) -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<!-- Development (CDN, uncompressed) -->
<script src="https://code.jquery.com/jquery-3.7.1.js"></script>
<!-- Fallback: load local if CDN fails -->
<script>
window.jQuery || document.write(
'<script src="js/jquery-3.7.1.min.js"><\/script>'
)
</script>
<!-- npm install -->
<!-- npm install jquery -->Document Ready
Wrap DOM-manipulating code in $(document).ready() so it runs only after the DOM tree is parsed (don't wait for images). $(fn) is the shorthand. If your <script> is at the end of <body>, the DOM is already available and you can skip ready. jQuery's ready is similar to DOMContentLoaded — it fires before window.load (which waits for images/iframes).
// run code after DOM is fully parsed
$(document).ready(function () {
console.log('DOM ready')
})
// shorter equivalent
$(function () {
console.log('DOM ready')
})
// modern preferred (no ready needed at end of body)
// <script> at the end of <body> runs after DOM exists
// pass a function to $ — jQuery treats it as ready handler
$(function () {
$('.btn').on('click', function () {
alert('clicked')
})
})The jQuery Object ($)
$ is just an alias for jQuery — both work identically. As a function, $ selects elements (returns a jQuery collection). As a namespace, $.foo provides utility methods. If another library uses $, call $.noConflict() to release it; you can still use jQuery or pass $ into a ready callback to alias it locally.
// $ is an alias for jQuery
console.log($ === jQuery) // true
// $ as a function: select elements
$('p') // all paragraphs
$('#main') // element with id main
$('.btn.primary') // elements with class btn and primary
// $ as a namespace: utility methods
$.each([1, 2, 3], function (i, v) { console.log(i, v) })
$.extend({}, defaults, options)
$.type([]) // 'array'
// noConflict: release $ back to other libraries
$.noConflict()
jQuery(function ($) { /* $ available here */ })Chaining Methods
Most jQuery methods return the same jQuery collection, so you can chain calls — jQuery's signature feature. Use .end() to pop back to the previous set after a traversal method (.find(), .filter()). Indentation makes chained traversals readable. Avoid overly long chains — they become hard to debug. For non-chainable methods (like .width() getter), the chain breaks because they return a value, not the collection.
// most methods return the jQuery object, enabling chains
$('.box')
.addClass('active')
.css('color', 'red')
.slideDown(300)
.text('Hello')
// break across lines for readability
$('#nav')
.find('.item')
.addClass('highlight')
.end()
.find('.link')
.attr('target', '_blank')
// .end() pops the current filter back to previous setNo-Conflict Mode
$.noConflict() returns control of $ to whichever library defined it first. After calling, use the full jQuery name, or assign jQuery to a custom variable. The IIFE pattern (function($){...})(jQuery) is the classic way to keep $ as a local alias inside your module while leaving the global $ for other libraries — essential in WordPress and legacy environments.
// release $ to other libraries (e.g., Prototype)
$.noConflict()
// use jQuery full name
jQuery('.btn').on('click', function () { /* ... */ })
// or assign jQuery to a custom alias
const jq = $.noConflict()
jq('.btn').hide()
// IIFE pattern: keep $ inside a local scope
(function ($) {
$('.btn').on('click', function () { /* ... */ })
})(jQuery)Selectors
Basic Selectors
jQuery uses CSS selectors (mostly compatible with querySelectorAll). The big three: tag name, #id, .class. Comma combines selectors (union). #id is the fastest (uses getElementById). Universal * is slow — avoid it on large documents. Selector results are always a jQuery collection (array-like), even if 0 or 1 elements match.
// element selector
$('div') // all <div> elements
$('p') // all <p> elements
// id selector
$('#header') // element with id="header"
// class selector
$('.btn') // all elements with class="btn"
$('.btn.primary') // .btn AND .primary
// multiple (comma = OR)
$('div, p, .btn') // divs, paragraphs, and .btn
// universal
$('*') // all elements (slow — avoid)Hierarchy & Combinators
Combinators describe relationships: space (descendant), > (direct child), + (adjacent sibling), ~ (general sibling). Right-to-left evaluation means $('ul li') finds all <li> first, then filters to those inside <ul> — so specific right-side selectors perform better. Avoid deep combinators on huge documents; scope to a container via $(container).find(...) for better perf.
// descendant: space (any depth)
$('ul li') // <li> anywhere inside <ul>
// child: > (direct children only)
$('ul > li') // <li> directly inside <ul>
// adjacent sibling: + (immediately next)
$('h2 + p') // <p> right after <h2>
// general sibling: ~ (all following siblings)
$('h2 ~ p') // all <p> siblings after <h2>
// multiple levels
$('#nav .item > a span')Attribute Selectors
Attribute selectors target elements by attribute values: [attr] (presence), [attr=val] (exact), [attr^=val] (starts), [attr$=val] (ends), [attr*=val] (contains), [attr~=val] (word). Quote values for safety. [attr!=val] is a jQuery-only extension (use .not('[attr=val]') for CSS compliance). Useful for forms (input types), links (href patterns), and data-* attributes.
// has attribute
$('[href]') // any element with href
$('[data-id]') // any element with data-id
// exact value
$('[type="text"]') // type="text"
$('[href="https://example.com"]')
// substring matches
$('[href^="https"]') // starts with "https"
$('[href$=".pdf"]') // ends with ".pdf"
$('[href*="example"]') // contains "example"
// whitespace-separated word
$('[class~="active"]') // class contains word "active"
// inequality (jQuery extension, not CSS)
$('[href!="https://x.com"]')Form & Input Selectors
Form pseudo-selectors (:input, :text, :checked, etc.) are jQuery extensions — not part of CSS, so they can't use querySelectorAll's fast path. For performance on large forms, use $('input[type=text]') instead of $(':text'). :checked and :selected are essential for reading form state. These are dynamic — they re-evaluate on each access.
// :input matches all form controls
$(':input') // input, textarea, select, button
// by type
$(':text') // <input type="text">
$(':password')
$(':checkbox')
$(':radio')
$(':submit')
$(':file')
// by state
$(':checked') // checked checkbox/radio or selected option
$(':selected') // selected <option>
$(':disabled')
$(':enabled')
$(':focus')
// combined with class
$('input.required:visible')Filter & Content Selectors
Filters narrow a selection: :first/:last, :even/:odd (0-indexed), :eq(n), :gt(n), :lt(n). Content filters (:contains, :has, :empty, :parent) match based on contents. :visible and :hidden are based on offsetWidth/Height and CSS (note: elements with visibility:hidden are :hidden in jQuery 3+; they used to be :visible). Many of these are jQuery extensions — prefer .filter(), .first(), .eq() methods for better performance.
// basic filters
$('li:first') // first <li>
$('li:last') // last <li>
$('li:even') // even-indexed (0, 2, 4...)
$('li:odd') // odd-indexed
$('li:eq(2)') // 3rd <li> (0-indexed)
$('li:gt(2)') // <li> after index 2
$('li:lt(2)') // <li> before index 2
// content filters
$('div:contains("Hello")') // divs containing text "Hello"
$('div:empty') // divs with no children
$('div:has(p)') // divs containing <p>
$('div:parent') // divs that have children
// visibility
$(':visible')
$(':hidden')Events
Binding Events with .on()
.on() is the modern, all-in-one event binder (replacing .bind/.delegate/.live). Pass event name(s) and a handler. 'this' inside the handler is the DOM element (not jQuery-wrapped — wrap with $(this) if needed). Use event namespaces (click.myapp) to unbind specific handlers without affecting others. Multiple events can share one handler (space-separated) or have separate handlers (object form).
// basic binding
$('.btn').on('click', function (e) {
console.log('clicked!', this) // 'this' is the DOM element
})
// multiple events, same handler
$('.box').on('mouseenter mouseleave', function () {
$(this).toggleClass('hover')
})
// multiple events, different handlers
$('.input').on({
focus: function () { $(this).addClass('focused') },
blur: function () { $(this).removeClass('focused') }
})
// named events (for targeted unbinding)
$('.btn').on('click.myapp', handler)
$('.btn').off('click.myapp') // only removes this oneEvent Delegation
Event delegation binds one handler to a parent that catches events from children via bubbling. The second selector to .on() ('li') filters which children trigger the handler. Benefits: one handler instead of N, automatic support for dynamically added elements, lower memory. Essential for long lists or SPAs. 'this' is the matched child (the li), not the parent. Use e.delegateTarget to access the parent.
// delegate: handle events on children, now or future
$('#list').on('click', 'li', function (e) {
// 'this' is the <li> that was clicked
$(this).toggleClass('done')
})
// works for <li> added AFTER binding too
$('#list').append('<li>New item (also clickable)</li>')
// WHY: instead of binding to each <li>,
// bind ONCE to the parent; clicks bubble up
// - 1 handler instead of N
// - works for dynamically added elements
// - lower memory, faster setupEvent Object & Methods
The event object (e) carries info and control methods. preventDefault stops the browser default (link navigation, form submit). stopPropagation stops bubbling; stopImmediatePropagation also stops other handlers on the same element. e.target is the actual clicked element (may be a child); e.currentTarget is the element with the handler (= this). Pass data to handlers via the optional second arg to .on().
$('.link').on('click', function (e) {
e.preventDefault() // stop default action (e.g., navigation)
e.stopPropagation() // stop bubbling to parents
e.stopImmediatePropagation() // stop other handlers on same element
console.log(e.type) // 'click'
console.log(e.target) // the actual clicked element
console.log(e.currentTarget) // the element with the handler (= this)
console.log(e.which) // mouse button or key code
console.log(e.pageX, e.pageY) // mouse coordinates
// data passed via .on(name, data, handler)
})
$('.btn').on('click', { greeting: 'hi' }, function (e) {
console.log(e.data.greeting) // 'hi'
})Shorthand Event Methods
Shorthand methods (.click, .focus, .submit, etc.) bind a handler when called with a function, or trigger the event when called without. .hover(enter, leave) is a convenience for mouseenter+mouseleave. These are equivalent to .on('event', fn) but shorter. Some shorthands (.load, .error) were removed in jQuery 3 — use .on('load', ...) instead. Prefer .on() for new code; it's more explicit and supports delegation.
// shorthand: bind a handler (or trigger if no handler)
$('.btn').click(function () { /* ... */ })
$('.btn').click() // trigger click
// common shorthands
$('.box').hover(enterFn, leaveFn) // mouseenter + mouseleave
$('.input').focus(function () { /* ... */ })
$('.input').blur(function () { /* ... */ })
$('.form').submit(function (e) {
e.preventDefault()
/* ... */
})
$('.el').mouseenter(fn).mouseleave(fn).mousemove(fn)
$(window).resize(fn).scroll(fn)
// deprecated: .load(), .unload(), .error() (removed in jQuery 3)Triggering & One-Time Events
.trigger('event') fires handlers AND native behavior (e.g., navigation); .triggerHandler() fires only jQuery-bound handlers and returns the handler's return value (not chainable). .one() binds a handler that auto-removes after the first invocation — perfect for first-click hints, one-time setup. Custom events let components communicate: $(document).trigger('myapp:ready') and listeners subscribe with .on('myapp:ready', fn).
// trigger an event programmatically
$('.btn').trigger('click')
$('.btn').click() // shorthand
// trigger with extra data
$('.box').trigger('custom', [1, 2, 3])
// custom events
$('.box').on('custom', function (e, a, b, c) {
console.log(a, b, c) // 1 2 3
})
// fire handler only once
$('.btn').one('click', function () {
alert('This fires only once')
})
// trigger only native handlers, not custom
$('.link').triggerHandler('click')DOM Manipulation
Get & Set Content
.html() works with HTML markup (like innerHTML) — tags are parsed. .text() is for plain text — tags are escaped and shown literally (safer against XSS for user input). .val() is for form controls (input, select, textarea). All three are getters (return the first element's value) when called with no arg, and setters (apply to all matched elements) when called with an arg. Setters accept a callback (index, oldValue) => newValue.
// .html(): get or set HTML content
$('#box').html() // get innerHTML
$('#box').html('<p>Hello</p>') // set innerHTML
// .text(): get or set text (HTML-escaped)
$('#box').text() // get text content
$('#box').text('<p>Not parsed</p>') // displays literally
// setter with callback (index, old value)
$('p').text(function (i, oldText) {
return i + ': ' + oldText
})
// .val(): form value
$('input').val() // get
$('input').val('new value') // set
$('select').val() // get selected value
$('select').val(['a', 'b']) // multi-selectInserting Elements
Create elements by passing HTML to $(): $('<div class="x">...</div>'). Append/prepend insert inside (at end/beginning); before/after insert outside (as siblings). The 'To' variants (appendTo, prependTo, insertBefore, insertAfter) reverse the subject — useful for chaining on the new element. Newly inserted elements inherit event handlers if bound via delegation, but not if bound directly before insertion.
// create new element
const $li = $('<li class="item">New item</li>')
// add as last child of each matched element
$('ul').append($li)
$('ul').append('<li>Direct string</li>')
// add as first child
$('ul').prepend('<li>First</li>')
// insert before/after matched elements
$('.box').before('<hr>')
$('.box').after('<hr>')
// reverse direction (insert target into container)
$('<p>Hi</p>').appendTo('#main') // same as $('#main').append(...)
$('<p>Hi</p>').insertBefore('#nav')Removing & Replacing
.remove() deletes elements and cleans up their data/events. .detach() removes from DOM but preserves data/events — useful for temporarily relocating elements. .empty() clears children but keeps the element. .replaceWith() swaps each element with new content. .unwrap() removes the parent, promoting the element up. Always use .remove() or .detach() — never just .html('') on a parent, which leaks data/events on children.
// remove element (and its data + events)
$('.box').remove()
// detach: remove but keep events/data (for re-insertion)
const $el = $('.box').detach()
// ... later
$('body').append($el) // events still work
// empty: remove all children (keep element)
$('.box').empty()
// replace with new content
$('.old').replaceWith('<div class="new">New</div>')
// unwrap: remove parent (keep element)
$('.box').unwrap()Wrapping & Cloning
.wrap() wraps each element individually; .wrapAll() wraps the whole set as one group; .wrapInner() wraps the contents (not the element itself). .clone() creates a deep copy — pass true to also clone event handlers and data (default false, so handlers don't fire on the copy). .unwrap() removes the immediate parent. These are powerful for restructuring markup without rewriting HTML strings.
// wrap each element in a structure
$('p').wrap('<div class="wrapper"></div>')
// each <p> becomes <div class="wrapper"><p>...</p></div>
// wrap all matched elements together
$('p').wrapAll('<div class="all"></div>')
// wrap inner contents
$('p').wrapInner('<span></span>')
// <p><span>...</span></p>
// clone (with events)
const $copy = $('.box').clone(true) // true = clone events too
$('body').append($copy)
// unwrap (remove wrapper)
$('.box').unwrap()Iterating & Filtering
.each() iterates — 'this' is the raw DOM element; return false to break, anything else to continue. .map() transforms to a new jQuery collection; call .get() to convert to a plain array. .filter() narrows by selector or callback; .not() is the inverse. .is() returns a boolean (does any element match?) — useful for conditionals. Avoid for loops with .eq(i); use .each() or .map() for idiomatic jQuery.
// .each: iterate (this = DOM element, i = index)
$('li').each(function (i, el) {
console.log(i, this, el) // el === this
if (i === 2) return false // break
// return true to continue
})
// .map: transform to new array
const ids = $('li').map(function (i, el) {
return $(el).data('id')
}).get() // .get() converts to plain array
// .filter: keep matching
$('li').filter('.active').addClass('on')
$('li').filter(function (i) { return i % 2 === 0 })
// .not: remove matching
$('li').not('.active').addClass('off')
// .is: returns boolean (does ANY match?)
if ($('li').is('.active')) { /* at least one is active */ }CSS Manipulation
Get & Set CSS Properties
.css() gets computed styles (always the resolved value, even from CSS rules) or sets inline styles. For getters, pass a single property name (returns a string). For setters, pass (prop, value), or an object of prop:value pairs. Property names can be camelCase or kebab-case (the latter needs quotes). Numeric values default to pixels for most properties. Avoid .css() for layout — use classes and stylesheets for maintainability.
// get computed style (first matched element)
$('.box').css('color') // 'rgb(255, 0, 0)'
$('.box').css('font-size') // '14px'
// set single property
$('.box').css('color', 'red')
// set multiple properties
$('.box').css({
color: 'red',
'background-color': '#eee', // kebab-case needs quotes
fontSize: '16px', // or use camelCase
marginTop: '10px'
})
// setter with callback (index, old value)
$('div').css('width', function (i, old) {
return (parseInt(old) + 50) + 'px'
})Class Manipulation
Class methods are the preferred way to style elements — they keep presentation in CSS, not JS. .addClass, .removeClass, .toggleClass accept space-separated class lists. .toggleClass(name, bool) adds/removes based on the boolean — useful for state-driven UI. .hasClass(name) returns a boolean (only checks the first element). For perf, classes are much faster than .css() for non-trivial styling because they batch changes and let the browser optimize.
// add class
$('.box').addClass('active')
// remove class
$('.box').removeClass('active')
// toggle (add if absent, remove if present)
$('.box').toggleClass('active')
// toggle based on boolean
$('.box').toggleClass('active', isValid)
// add/remove multiple (space-separated)
$('.box').addClass('active highlighted urgent')
$('.box').removeClass('active highlighted')
// check if has class (boolean)
if ($('.box').hasClass('active')) { /* ... */ }
// callback form (index, hasClass)
$('div').toggleClass('even', function (i) {
return i % 2 === 0
})Width & Height
.width()/.height() = content box (no padding/border). .innerWidth()/.innerHeight() = content + padding. .outerWidth()/.outerHeight() = content + padding + border. .outerWidth(true) includes margin. Setters take a number (px) or string. For window/document, use $(window) for viewport and $(document) for the full page. These return numbers (not strings like .css('width')), which is convenient for math.
// content size (excludes padding, border, margin)
$('.box').width() // e.g., 200
$('.box').width(300) // set content width to 300px
$('.box').height()
// inner (content + padding)
$('.box').innerWidth()
$('.box').innerHeight()
// outer (content + padding + border)
$('.box').outerWidth()
$('.box').outerHeight()
// outer including margin
$('.box').outerWidth(true)
// window & document
$(window).width() // viewport width
$(document).height() // full document heightPosition & Offset
.offset() returns {top, left} relative to the document (useful for absolute positioning or drag-drop). .position() returns coordinates relative to the offset parent (the nearest ancestor with position: relative/absolute/fixed). .scrollTop()/.scrollLeft() get or set scroll position — $(window).scrollTop(0) scrolls to top. .offsetParent() finds the positioned ancestor. These are essential for animations, drag-drop, and infinite scroll.
// .offset(): position relative to document
const off = $('.box').offset()
console.log(off.left, off.top) // e.g., 100, 50
$('.box').offset({ top: 200, left: 100 }) // set
// .position(): position relative to offset parent
const pos = $('.box').position()
console.log(pos.left, pos.top)
// .scrollTop() / .scrollLeft(): scroll position
$(window).scrollTop() // how far scrolled down
$(window).scrollTop(0) // scroll to top
$('.container').scrollTop(100)
// .offsetParent(): nearest positioned ancestor
$('.box').offsetParent()Scroll & Coordinates
Scroll handling is common for sticky navs, infinite scroll, and scroll-triggered animations. $('html, body').animate({scrollTop: n}) smoothly scrolls. For perf, throttle scroll handlers (they fire many times per scroll). The isVisible helper checks if an element is within the viewport — useful for lazy-loading images or triggering animations. Modern IntersectionObserver is more efficient for visibility detection; consider it for new projects.
// scroll to top smoothly
$('html, body').animate({ scrollTop: 0 }, 500)
// scroll to an element
const top = $('#section').offset().top
$('html, body').animate({ scrollTop: top }, 500)
// detect scroll position
$(window).on('scroll', function () {
const scrolled = $(window).scrollTop()
if (scrolled > 100) {
$('.nav').addClass('fixed')
} else {
$('.nav').removeClass('fixed')
}
})
// check if element is in viewport
function isVisible($el) {
const t = $el.offset().top
const h = $el.outerHeight()
const wTop = $(window).scrollTop()
const wH = $(window).height()
return t >= wTop && (t + h) <= (wTop + wH)
}AJAX
$.ajax (Full Control)
$.ajax is the low-level, full-control method. method/http verb, data (sent as query for GET or body for POST), dataType hints the expected response (auto-parsed). success/error/complete are the legacy callback hooks. beforeSend lets you modify the xhr (add headers). timeout aborts after N ms. For modern code, prefer fetch() or $.ajax with .done()/.fail() promises instead of success/error callbacks.
$.ajax({
url: '/api/users',
method: 'GET', // or 'POST', 'PUT', 'DELETE'
data: { page: 1, limit: 10 }, // query string or body
dataType: 'json', // expected response type
contentType: 'application/json',
timeout: 5000, // ms
headers: { 'X-Token': 'abc' },
beforeSend: function (xhr) { /* ... */ },
success: function (data, status, xhr) {
console.log('got', data)
},
error: function (xhr, status, error) {
console.error('failed', status, error)
},
complete: function (xhr, status) {
// runs on both success and error
}
})Shorthand: $.get & $.post
$.get and $.post are shorthand for simple GET/POST requests. The promise-like interface (.done/.fail/.always) is cleaner than success/error callbacks — and supports chaining multiple .done handlers. For JSON request bodies, you must set contentType: 'application/json' AND JSON.stringify the data (jQuery doesn't auto-stringify objects as JSON). Note: jQuery's promise is a Deferred, not a native Promise, but works similarly for most cases.
// GET request
$.get('/api/users', { page: 1 }, function (data) {
console.log(data)
}, 'json')
// promise-based (preferred)
$.get('/api/users', { page: 1 })
.done(function (data) { console.log(data) })
.fail(function (xhr, status, err) { console.error(err) })
// POST request
$.post('/api/users', { name: 'Alice' })
.done(function (data) { /* created */ })
// POST JSON
$.ajax({
url: '/api/users',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({ name: 'Alice' })
})Loading HTML into Elements
.load() is a specialized shorthand: fetch HTML from a URL and inject it into matched elements. The 'url #fragment' syntax loads only a portion of the response (the part matching #fragment). .load() is great for partial-page updates without a full reload — common in legacy SPAs. It's GET by default, POST if you pass data. The callback receives (responseText, status, xhr). For modern apps, fetch + manual DOM update is preferred.
<!-- load HTML from server into element -->
<div id="content">Loading...</div>
<script>
$('#content').load('/partials/header.html')
// load a fragment (selector after URL space)
$('#content').load('/page.html #main')
// with callback (response, status, xhr)
$('#content').load('/page.html', function (resp, status, xhr) {
if (status === 'error') {
$('#content').html('Failed to load')
}
})
// POST with data
$('#content').load('/api', { id: 5 })
</script>getJSON & getScript
$.getJSON is shorthand for $.ajax with dataType:'json' — auto-parses the response. The '?callback=?' trick enables JSONP for cross-domain requests (legacy — use CORS instead now). $.getScript fetches and executes a JavaScript file — useful for lazy-loading plugins or analytics on demand. Both return jqXHR (promise-like) objects. For cross-domain requests today, ensure the server sends CORS headers; JSONP is a pre-CORS workaround with security caveats.
// fetch and parse JSON
$.getJSON('/api/users', { page: 1 })
.done(function (data) {
console.log(data.users)
})
.fail(function (xhr, status, err) {
console.error('JSON fetch failed:', err)
})
// fetch and execute a script
$.getScript('/js/analytics.js')
.done(function (script, status) {
console.log('analytics loaded')
// script is now available
})
.fail(function () {
console.error('script failed')
})
// JSONP (cross-domain, legacy)
$.getJSON('https://api.example.com/data?callback=?')
.done(function (data) { /* ... */ })Global AJAX Events & Setup
Global AJAX events (ajaxStart, ajaxStop, ajaxComplete, ajaxError, ajaxSuccess) fire for every AJAX request — perfect for global loaders and error handling. $.ajaxSetup sets defaults applied to all subsequent AJAX calls (URL base, headers, timeout). .serialize() turns a form into a query string; .serializeArray() returns an array of {name, value} objects — useful for building JSON or custom payloads. Always include a CSRF token for state-changing requests.
// global ajax event handlers (bind on document)
$(document)
.ajaxStart(function () { $('#loader').show() })
.ajaxStop(function () { $('#loader').hide() })
.ajaxError(function (e, xhr, settings, error) {
console.error('AJAX error:', settings.url, error)
})
// default settings for all AJAX
$.ajaxSetup({
baseURL: '/api',
timeout: 10000,
headers: { 'X-CSRF-Token': $('meta[name=csrf]').attr('content') }
})
// now $.get('/users') -> GET /api/users with the headers
$.get('/users').done(/* ... */)
// serialize a form for AJAX
const data = $('form').serialize() // "name=Alice&age=30"
const arr = $('form').serializeArray() // [{name, value}, ...]Effects & Animations
Basic Show/Hide
.show()/.hide()/.toggle() change display. Without args, instantly show/hide. With a duration, animate width/height/opacity. 'slow' = 600ms, 'fast' = 200ms, or a number in ms. The callback fires when the animation completes. jQuery animations use requestAnimationFrame by default (jank-free). Note: .hide() sets display:none; .show() restores the original display value. For more control, use .animate() directly.
// instantly show/hide/toggle
$('.box').show()
$('.box').hide()
$('.box').toggle()
// animated (duration in ms or 'slow'/'fast')
$('.box').show(400) // fade in over 400ms
$('.box').hide('slow') // ~600ms
$('.box').toggle(300)
// with callback (runs when animation completes)
$('.box').hide(300, function () {
console.log('hidden')
})
// easing (default 'swing', also 'linear' with plugin)
$('.box').hide({ duration: 300, easing: 'linear' })Fading
Fade animations only change opacity (display stays). .fadeTo(animates to a specific opacity — useful for dimming elements without fully hiding them. Animations are queued per element: chaining .fadeOut().fadeIn() runs them sequentially, not simultaneously. To run in parallel, use .animate() with multiple properties or the queue option. Fading is smoother than show/hide for opacity-driven UI like notifications.
// fade in / out / toggle (opacity only)
$('.box').fadeIn(400)
$('.box').fadeOut(400)
$('.box').fadeToggle(400)
// fade to a specific opacity (0.0 - 1.0)
$('.box').fadeTo(400, 0.5) // 50% opacity
// with callback
$('.box').fadeOut(300, function () {
// runs after fade out completes
$(this).remove()
})
// sequence: fade out, then fade in
$('.box')
.fadeOut(300)
.fadeIn(300) // queued automaticallySliding
Sliding animates height (and display). .slideDown reveals an element (from display:none to its natural height), .slideUp hides it (collapses to 0 then display:none), .slideToggle flips state. Perfect for accordions, dropdowns, and collapsible panels. Combined with siblings() for accordion behavior (close others when one opens). Sliding is the most common jQuery animation — smooth and accessible-friendly when paired with proper ARIA.
// slide down (reveal) / up (hide) / toggle
$('.panel').slideDown(400)
$('.panel').slideUp(400)
$('.panel').slideToggle(400)
// accordion pattern
$('.accordion .header').on('click', function () {
$(this).next('.content').slideToggle(300)
.siblings('.content').slideUp(300)
})
// callback after slide completes
$('.panel').slideUp(300, function () {
console.log('panel closed')
})Custom .animate()
.animate() tweens numeric CSS properties over a duration. Use relative values ('+=50') for incremental changes. Property names must be camelCase (marginLeft, not margin-left). Colors require the jQuery Color plugin or jQuery UI — core jQuery only animates numbers. The options form gives more control: queue:false runs in parallel instead of sequentially. Easing defaults to 'swing' (ease-out); 'linear' is uniform. Many properties can be animated in one call.
// animate numeric CSS properties
$('.box').animate({
width: '+=50', // relative: add 50px
height: '300px',
opacity: 0.5,
marginLeft: '20px', // camelCase for hyphenated props
fontSize: '20px'
}, 800, 'swing', function () {
console.log('animation done')
})
// options object form
$('.box').animate({
left: 500
}, {
duration: 1000,
easing: 'linear',
complete: function () { /* done */ },
queue: false // run immediately, not queued
})
// NOTE: colors can't be animated without jQuery UI / color pluginStopping & Chaining
.stop() halts animations; (clearQueue, jumpToEnd) gives control. Without stop, rapid hovers stack animations (the element keeps animating long after the mouse leaves) — .stop(true) before .animate() prevents this. .finish() jumps all queued animations to their final state instantly. .delay(ms) inserts a pause in the animation queue — useful for sequencing. Always pair hover animations with .stop() to avoid janky buildup.
// .stop(): stop current animation
$('.box').stop() // stop, leave at current state
$('.box').stop(true) // clear queue too
$('.box').stop(true, true) // clear queue AND jump to end
// prevent animation buildup on rapid hover
$('.box').hover(function () {
$(this).stop(true).animate({ width: 300 }, 200)
}, function () {
$(this).stop(true).animate({ width: 100 }, 200)
})
// .finish(): jump all queued animations to end state
$('.box').finish()
// .delay(): pause between queued animations
$('.box')
.fadeOut(300)
.delay(500) // wait 500ms
.fadeIn(300)Traversing
Moving Up (Parents)
Traversing up: .parent() (immediate parent), .parents() (all ancestors up to document), .parents(selector) filters them, .closest(selector) walks up and returns the first match (including self — most common for finding an enclosing component). .parentsUntil(selector) returns ancestors up to but not including the match. .closest() is the workhorse for event delegation patterns — given a clicked element, find its enclosing card/row/component.
// direct parent
$('#item').parent()
// all ancestors (up to <html> or selector)
$('#item').parents() // all ancestors
$('#item').parents('.container') // first .container ancestor
// closest ancestor (incl. self) matching selector
$('#item').closest('.box') // very common - find enclosing box
// parentsUntil: ancestors up to (not including) match
$('#item').parentsUntil('.wrapper')
// offsetParent: nearest positioned ancestor
$('#item').offsetParent()Moving Down (Children)
.children() returns direct children only (one level down); .find() descends through all levels. .find() is the most common — given a container, find matching descendants (e.g., find all form fields inside a form). .children() is faster when you only need direct children. .contents() includes text nodes and comment nodes (useful for processing text or iframes). Filter with a selector for both. Always scope traversals with .find() to avoid scanning the entire document.
// direct children (optionally filtered)
$('#list').children() // all direct children
$('#list').children('.item') // direct children with class .item
// all descendants (filtered)
$('#list').find('li') // all <li> anywhere inside
$('#list').find('.active') // all .active descendants
// contents: children including text/comment nodes
$('#box').contents()
// first/last child
$('#list').children().first()
$('#list').children().last()Moving Sideways (Siblings)
Sibling traversals: .siblings() (all), .next()/.prev() (immediate), .nextAll()/.prevAll() (all in one direction), .nextUntil()/.prevUntil() (up to a match). Filter with a selector. The classic active-tab pattern ($(this).addClass('active').siblings().removeClass('active')) uses siblings to reset siblings' state. Siblings share the same parent — if elements are nested differently, they're not siblings.
// all siblings (excluding self)
$('#item').siblings()
$('#item').siblings('.active') // siblings with class .active
// next/prev: immediate sibling
$('#item').next()
$('#item').prev()
// nextAll/prevAll: all following/preceding siblings
$('#item').nextAll()
$('#item').prevAll('.item')
// nextUntil/prevUntil: siblings up to a match
$('#item').nextUntil('.stop')
// common: highlight active tab
$('.tab').on('click', function () {
$(this).addClass('active').siblings().removeClass('active')
})Filtering a Selection
Filtering narrows the current set without re-querying the DOM. .filter(selector) keeps matches; .not(selector) is the inverse. .has(selector) keeps elements containing a matching descendant. .eq(n) reduces to one element (negative n counts from end). .first()/.last() are conveniences. .slice(start, end) takes a range. Use these to refine a selection after traversal instead of writing complex selectors — often clearer and faster.
// .filter: keep matching
$('li').filter('.active')
$('li').filter(function (i) { return i % 2 === 0 }) // even index
// .not: remove matching (inverse of filter)
$('li').not('.active')
$('li').not(':first')
// .has: keep elements that contain a descendant
$('div').has('p') // divs that contain <p>
// .eq: reduce to one element by index
$('li').eq(2) // 3rd <li> (0-indexed)
$('li').eq(-1) // last <li>
// .first / .last
$('li').first()
$('li').last()
// .slice: range
$('li').slice(2, 5) // indices 2, 3, 4Chaining with .end() & .addBack()
.end() pops the traversal stack, returning to the previous set — essential for readable chains that traverse and then continue working on the original. .addBack() (formerly .andSelf) merges the current set with the previous set, so the original element participates in subsequent operations. These let you build powerful one-liner chains: find children, modify them, end() back to parent, modify the parent. Indent chains to make the traversal structure clear.
// .end(): pop back to previous set after a traversal
$('ul') // [ul]
.find('li') // [li, li, li]
.addClass('item')
.end() // back to [ul]
.addClass('has-items')
// .addBack(): include previous set in current
$('ul')
.children('li') // [li, li]
.addClass('item')
.addBack() // [li, li, ul]
.addClass('related')
// without .addBack, the ul would not get .related
// .andSelf (old name) is deprecated, use .addBackUtility Functions
Iteration: $.each & $.map
$.each iterates arrays (index, value) or objects (key, value) — note the arg order is reversed vs Array.forEach. Return false to break. $.map transforms each item into a new array (return null to skip). $.grep filters an array. These are jQuery's pre-ES5 iteration utilities; modern code often uses native Array methods (forEach, map, filter) instead. The main reason to use $.each is iterating plain objects (Object.entries in modern JS).
// $.each: iterate arrays or objects (returns the collection)
$.each([10, 20, 30], function (index, value) {
console.log(index, value) // 0 10, 1 20, 2 30
if (value === 20) return false // break
})
$.each({ a: 1, b: 2 }, function (key, value) {
console.log(key, value) // 'a' 1, 'b' 2
})
// $.map: transform to a new array
const doubled = $.map([1, 2, 3], function (value, index) {
return value * 2 // [2, 4, 6]
})
// return null/undefined to skip
const evens = $.map([1, 2, 3, 4], function (v) {
return v % 2 === 0 ? v : null // [2, 4]
})
// $.grep: filter an array
const evens = $.grep([1, 2, 3, 4], function (v) { return v % 2 === 0 })
// [2, 4]Type Checking
$.type returns a lowercase type string (more granular than typeof). $.isArray, $.isFunction, $.isPlainObject, $.isEmptyObject, $.isWindow, $.isNumeric are convenience booleans. $.isPlainObject distinguishes plain {} from instances like new Date or window. $.isNumeric returns true for finite numbers and numeric strings (not for hex like '0x1F' in newer jQuery). In modern JS, Array.isArray and typeof cover most needs, but $.isPlainObject is still handy for deep-extend safety.
// $.type: detailed type
$.type([]) // 'array'
$.type({}) // 'object'
$.type('hi') // 'string'
$.type(null) // 'null'
$.type(undefined) // 'undefined'
$.type(/regex/) // 'regexp'
// specific checks (all return boolean)
$.isArray([]) // true
$.isFunction(fn => {}) // true
$.isEmptyObject({}) // true
$.isPlainObject({}) // true (created by {} or new Object)
$.isPlainObject([]) // false
$.isWindow(window) // true
$.isNumeric('42') // true (also '0x1F')
$.isNumeric('abc') // false
// $.isEmptyObject: no own enumerable props
$.isEmptyObject({ a: 1 }) // falseObject & Array Utilities
$.extend merges objects — later properties override earlier. Pass true as the first arg for deep (recursive) merge; otherwise it's shallow. Always pass {} as the first arg to avoid mutating the source. $.merge concatenates arrays (mutates the first). $.inArray is indexOf (returns -1 if not found). $.makeArray converts array-like objects (NodeList, arguments) to true arrays — modern code uses Array.from or spread [...nl] instead.
// $.extend: merge objects (mutates first unless deep copy flag)
const defaults = { a: 1, b: 2 }
const options = { b: 3, c: 4 }
const merged = $.extend({}, defaults, options)
// { a: 1, b: 3, c: 4 } (later overrides earlier)
// deep merge (recurses into nested objects)
const deep = $.extend(true, {},
{ nested: { x: 1, y: 2 } },
{ nested: { y: 3, z: 4 } }
)
// { nested: { x: 1, y: 3, z: 4 } }
// $.merge: combine two arrays (mutates first)
const combined = $.merge([1, 2], [3, 4]) // [1, 2, 3, 4]
// $.inArray: find index (-1 if not found)
const idx = $.inArray(2, [1, 2, 3]) // 1
// $.makeArray: convert array-like to true array
const arr = $.makeArray(document.querySelectorAll('li'))String & Data Utilities
$.trim is whitespace strip (modern: str.trim()). $.parseHTML converts an HTML string to an array of DOM nodes — safer than $.fn.html for untrusted input (avoids script execution). $.param serializes an object to a query string (inverse of parse). $.now is Date.now(). $.unique dedupes DOM element arrays (rarely needed now). Most of these have native modern equivalents; the most useful remaining are $.param (for AJAX data) and $.parseHTML.
// $.trim: remove leading/trailing whitespace
$.trim(' hello ') // 'hello' (use str.trim() in modern code)
// $.parseHTML: parse string to DOM nodes
const nodes = $.parseHTML('<div><p>Hi</p></div>')
// [div] — array of DOM nodes
// $.parseJSON: parse JSON string (use JSON.parse instead)
const obj = $.parseJSON('{"a":1}')
// $.param: serialize object to query string
$.param({ name: 'Alice', age: 30 })
// 'name=Alice&age=30'
$.param({ tags: ['a', 'b'] })
// 'tags%5B%5D=a&tags%5B%5D=b'
// $.now: current timestamp (Date.now equivalent)
$.now() // 1700000000000
// $.unique: remove duplicates from DOM element array
$.unique($('.box').get())Data Storage ($.data)
$.data / .data() stores arbitrary values associated with elements in an internal cache (not visible in the DOM, faster than attributes). .data() also auto-reads data-* attributes (with type casting: numbers, booleans, JSON). Important: .data(name, value) writes to the cache, NOT the data-* attribute — use .attr('data-name', val) to update the attribute itself. .removeData clears the cache entry. The cache is cleaned up when elements are .remove()'d, preventing leaks.
// store data on an element (private, won't appear in DOM)
$.data(document.getElementById('box'), 'key', { count: 1 })
// read it back
const v = $.data(el, 'key') // { count: 1 }
//.data() method form (more common)
$('.box').data('key', { count: 1 })
$('.box').data('key') // { count: 1 }
// auto-parses data-* attributes
// <div class="box" data-id="42" data-name="alice"></div>
$('.box').data('id') // 42 (auto-cast to number)
$('.box').data('name') // 'alice'
// remove data
$('.box').removeData('key')
// note: .data() does NOT write back to data-* attrs
// use .attr('data-key', val) to update the attributePlugins & Extensions
Writing a Plugin
Plugins extend $.fn (jQuery's prototype) so they're callable on collections. Always return this (or the result of a method on this) for chainability. Inside the plugin, 'this' is the jQuery collection; inside .each(), 'this' is a raw DOM element. Wrap in an IIFE to safely alias $ via jQuery. The $.extend defaults pattern is the standard way to accept options with sensible defaults. Publish plugins via npm or as standalone scripts.
// basic plugin pattern
(function ($) {
$.fn.highlight = function (color) {
// 'this' is the jQuery collection
return this.css('background', color || 'yellow')
}
}(jQuery))
// usage
$('.box').highlight('red')
$('.box').highlight() // default 'yellow'
// options pattern
$.fn.tooltip = function (options) {
const settings = $.extend({
position: 'top',
delay: 200
}, options)
return this.each(function () {
// 'this' is a DOM element here
// ... tooltip logic ...
})
}Plugin with Methods
The boilerplate-plugin pattern uses a methods object dispatched by the first argument: $('.c').counter({ start: 5 }) for init, $('.c').counter('increment') to call a method. State lives in $.data. This pattern was popular pre-Vue/React era for components like sliders, modals, datepickers. Today, a framework (Vue/React) is usually a better choice for stateful UI; but for augmenting legacy jQuery sites, this pattern is still useful and well-understood.
// stateful plugin using data + methods
(function ($) {
$.fn.counter = function (method) {
const methods = {
init: function (options) {
return this.each(function () {
const $el = $(this)
$el.data('count', options?.start || 0)
$el.text($el.data('count'))
})
},
increment: function () {
return this.each(function () {
const $el = $(this)
const c = $el.data('count') + 1
$el.data('count', c).text(c)
})
},
reset: function () {
return this.each(function () {
$(this).data('count', 0).text(0)
})
}
}
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1))
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments)
} else {
$.error('Method ' + method + ' does not exist')
}
}
}(jQuery))Using Popular Plugins
jQuery UI is the official companion library for widgets (datepicker, accordion, autocomplete, dialog) and interactions (draggable, droppable, sortable). Other popular plugins: DataTables (feature-rich tables), Select2 (enhanced selects), Slick (carousels), Magnific Popup (modals). Include plugin CSS and JS after jQuery. Initialize on DOM ready. Many plugins support a method API like $('#x').plugin('method', args) and option updates via plugin('option', name, val).
<!-- jQuery UI (interactions, widgets, effects) -->
<link rel="stylesheet" href="https://code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.min.js"></script>
<script>
// datepicker
$('#date').datepicker({ dateFormat: 'yy-mm-dd' })
// draggable / droppable / sortable
$('.item').draggable()
$('.list').sortable()
// dialog
$('<div>Hello</div>').dialog({ modal: true })
</script>
<!-- Other common plugins -->
<!-- DataTables: tables with sorting, search, pagination -->
<!-- Select2: enhanced <select> with search -->
<!-- Slick: carousel/slider -->
<!-- Magnific Popup: lightbox/modal -->Widget Factory (jQuery UI)
The jQuery UI Widget Factory ($.widget) is a structured way to build stateful, inheritance-friendly plugins. It gives you _create (constructor), _destroy (cleanup), _setOption, _on (auto-cleanup event binding), _trigger (custom events). Options are exposed via the method API: $el.widget('option', name, value). Custom events fire as widgetname + eventname. For complex stateful UI in jQuery, the widget factory is the most robust pattern — but consider migrating to Vue/React for new code.
// create a reusable widget with state + inheritance
$.widget('my.counter', {
options: { start: 0 },
_create: function () {
this.count = this.options.start
this._refresh()
this._on(this.element, { click: 'increment' })
},
increment: function () {
this.count++
this._refresh()
this._trigger('changed', null, { count: this.count })
},
_refresh: function () {
this.element.text(this.count)
},
_destroy: function () {
this.element.text('')
}
})
// usage
$('#el').counter({ start: 5 }) // init
$('#el').counter('increment') // call method
$('#el').on('counterchanged', function (e, data) {
console.log('count is', data.count)
})
$('#el').counter('destroy') // cleanupPlugin Best Practices
Plugin best practices: (1) always return this for chainability; (2) wrap in IIFE for $ safety; (3) use .each() so the plugin works on multi-element selections; (4) namespace events (click.myplugin) so users can unbind without affecting others; (5) merge options with $.extend and expose defaults so users can globally override; (6) clean up on destroy (events, data, DOM modifications); (7) namespace the plugin name to avoid collisions; (8) expose public methods and defaults for flexibility.
// 1. always return this for chainability
$.fn.green = function () { return this.css('color', 'green') }
// 2. use IIFE for $ safety
(function ($) { /* plugin */ })(jQuery)
// 3. iterate with .each() for multi-element support
$.fn.x = function () { return this.each(function () { /* per element */ }) }
// 4. namespace events so users can unbind cleanly
this._on(this.element, { 'click.myplugin': 'handler' })
// 5. accept options via $.extend
const opts = $.extend({}, $.fn.x.defaults, options)
$.fn.x.defaults = { color: 'red' }
// 6. clean up on destroy (events, data, DOM)
// 7. namespace your plugin name to avoid collisions
// 8. provide public methods + defaults accessible for overrideAttributes & Properties
Attribute Getters & Setters
.attr() reads/writes HTML attributes (the markup values, always strings). .prop() reads/writes DOM properties (current state — e.g., the checked property of a checkbox may differ from its checked attribute after user interaction). Use .prop() for boolean attributes (checked, disabled, selected) and .attr() for others (href, src, alt, data-*). .data() reads data-* but stores values in an internal cache with type preservation — use .attr('data-*') if you need to update the DOM attribute itself.
// .attr: HTML attribute (what's in the markup)
$('img').attr('src') // get
$('img').attr('src', 'new.jpg') // set
$('img').attr({ // set multiple
src: 'new.jpg',
alt: 'Photo',
width: 200
})
// .removeAttr: remove attribute entirely
$('img').removeAttr('alt')
// .prop: DOM property (current state, may differ from attr)
$('input').prop('checked') // true/false (current state)
$('input').prop('checked', true) // check it
$('input').prop('disabled', true)
// data-* attributes
$('div').attr('data-id', 42) // sets the attribute (string)
$('div').data('id', 42) // stores in cache (typed)Data Attributes
.data(name) reads data-* attributes with automatic type conversion (numbers, booleans, JSON). Setting via .data(name, value) writes to an internal cache, NOT the attribute — so .attr('data-name') still returns the original. To update the actual DOM attribute (e.g., for CSS selectors or server-side reading), use .attr('data-name', value). This distinction causes bugs if confused. .removeData clears the cache. Camel-case names: data-foo-bar becomes .data('fooBar').
<!-- HTML -->
<div class="box" data-id="42" data-name="alice" data-active="true" data-config='{"x":1}'></div>
<script>
// .data() reads data-* with auto type conversion
$('.box').data('id') // 42 (number, not string)
$('.box').data('name') // 'alice'
$('.box').data('active') // true (boolean)
$('.box').data('config') // { x: 1 } (parsed JSON)
// set data (stored in cache, NOT the attribute)
$('.box').data('id', 99)
$('.box').data('id') // 99
$('.box').attr('data-id') // still '42' (attribute unchanged)
// to update the attribute itself, use .attr
$('.box').attr('data-id', 99)
// remove data
$('.box').removeData('id')
</script>Form Properties
For form state, use .prop() (boolean DOM properties) — .prop('checked') reflects the current state, while .attr('checked') returns the initial HTML attribute (doesn't update on user interaction). .val() gets/sets form values uniformly. For radios, find the :checked one and read its .val(). For multi-select, .val() returns an array. .is(':checked') is a clean boolean check. These distinctions matter for form validation and serialization.
// checkbox state
$('input[type=checkbox]').prop('checked') // true/false
$('input[type=checkbox]').prop('checked', true)
$('input[type=checkbox]').is(':checked') // boolean
// disabled / readonly
$('input').prop('disabled', true)
$('input').prop('readOnly', true) // note: camelCase
// select value
$('select').val() // selected value
$('select').val('option2') // set selected
$('select option:selected').text() // selected text
// multiple select
$('select[multiple]').val() // ['a', 'b'] array
// radio: value of checked
$('input[name=gender]:checked').val()
//.prop vs .attr for checkboxes:
// .prop('checked') = current state (changes when clicked)
// .attr('checked') = initial HTML value (doesn't change)Class vs Attribute
Three ways to expose element state to CSS and JS: classes (well-known, .hasClass), data-* attributes (queryable, value-carrying), and ARIA attributes (accessible to screen readers). For visual state, classes are simplest. For multi-value state (data-state='loading|success|error'), data-* is cleaner. For accessibility, use aria-* (aria-pressed, aria-expanded, aria-busy) — they often overlap with visual state, so pair them. Modern frameworks make this easier, but in jQuery, be intentional about which you choose.
// adding behavior tags via class
$('.btn').addClass('loading')
// CSS: .btn.loading { opacity: 0.5; pointer-events: none; }
// via data attribute (cleaner for state)
$('.btn').attr('data-state', 'loading')
// CSS: .btn[data-state="loading"] { ... }
// via aria (accessible)
$('.btn').attr('aria-busy', 'true')
// choosing:
// - class: many CSS hooks, well-known
// - data-*: cleaner for state values, queryable
// - aria-*: exposes state to screen readers
// toggling example
$('.btn').on('click', function () {
const $b = $(this)
$b.attr('aria-pressed', $b.attr('aria-pressed') !== 'true')
})Value (Form Fields)
.val() is the universal getter/setter for form values — text inputs, textareas, selects, and (for radios/checkboxes) the value of the checked one. .serialize() produces a URL-encoded string; .serializeArray() gives a {name, value} array — convert to a plain object with .serializeArray().reduce((o, p) => (o[p.name] = p.value, o), {}). To reset a form, call the native .reset() method on the raw DOM element (jQuery has no .reset() method).
// text input
$('input[type=text]').val() // get current value
$('input[type=text]').val('new value') // set
// textarea
$('textarea').val()
// select
$('select').val() // selected value
$('select').val('opt2') // set
$('select').val(['a', 'b']) // multi-select set
// checkbox (use prop for state)
$('input[type=checkbox]').prop('checked')
// radio
$('input[type=radio][name=g]:checked').val()
// serialize whole form
$('form').serialize() // 'name=Alice&age=30'
$('form').serializeArray() // [{name, value}]
// reset form
$('form')[0].reset()Dimensions & Position
Width & Height Methods
Width/height methods return numbers (px), unlike .css('width') which returns strings with units. .width() = content box (CSS width). .innerWidth() = content + padding. .outerWidth() = content + padding + border. .outerWidth(true) includes margin. For window/document, $(window).width() is the viewport (visible area); $(document).height() is the full page height (can be larger than viewport when scrolled). Setters take numbers (px) or strings ('50%', '10em').
// content box (no padding, border, margin)
$('.box').width() // 200 (number, px)
$('.box').width(300) // set to 300px
// inner (content + padding)
$('.box').innerWidth()
$('.box').innerHeight()
// outer (content + padding + border)
$('.box').outerWidth()
$('.box').outerHeight()
// outer + margin
$('.box').outerWidth(true)
$('.box').outerHeight(true)
// window & document
$(window).width() // viewport width
$(window).height() // viewport height
$(document).height() // document height (may exceed viewport)Position vs Offset
.offset() is document-relative (absolute coordinates — useful for drag-drop, tooltips positioned at mouse). .position() is relative to the offset parent (the nearest ancestor with position:relative/absolute/fixed) — useful for repositioning within a container. .offsetParent() finds that ancestor. .scrollTop()/.scrollLeft() get or set scroll position — $(window).scrollTop() is page scroll. The distinction between offset (document) and position (parent) is critical for correct positioning math.
// .offset(): relative to document (top-left of page)
const o = $('.box').offset()
console.log(o.left, o.top) // e.g., 100, 200
$('.box').offset({ top: 0, left: 0 }) // move to top-left of page
// .position(): relative to offset parent
const p = $('.box').position()
console.log(p.left, p.top) // position within parent
// .scrollTop() / .scrollLeft()
$(window).scrollTop() // how far page is scrolled down
$(window).scrollTop(0) // scroll to top
$('.container').scrollTop(100) // scroll inner container
// .offsetParent(): nearest positioned ancestor
$('.box').offsetParent() // the element .position() is relative toResponsive Layout Helpers
Resize fires many times during a drag — debounce with setTimeout to avoid performance issues. Adding body classes (mobile/desktop) lets CSS respond via body.mobile .nav { ... }. For modern responsive code, prefer CSS media queries for styling and window.matchMedia() in JS — they're more performant and align with CSS breakpoints. jQuery's resize approach is fine for legacy code, but CSS + matchMedia is the modern standard.
// check viewport width on resize
$(window).on('resize', function () {
const w = $(window).width()
if (w < 768) {
$('body').addClass('mobile').removeClass('desktop')
} else {
$('body').addClass('desktop').removeClass('mobile')
}
}).resize() // trigger once to initialize
// debounced resize (avoid firing hundreds of times)
let timer
$(window).on('resize', function () {
clearTimeout(timer)
timer = setTimeout(function () {
console.log('resize settled')
}, 200)
})
// match media (preferred for responsive checks)
if (window.matchMedia('(max-width: 768px)').matches) {
// mobile
}Element Visibility Detection
:visible/:hidden check display, visibility, opacity, and dimensions — jQuery 3+ treats visibility:hidden and opacity:0 as hidden (older versions treated them as visible). For viewport detection, the inViewport helper compares element bounds against scroll position. Modern code uses IntersectionObserver for this — it's much more efficient (browser does the math, callbacks only on changes) and doesn't require scroll listeners. The jQuery approach works but fires on every scroll event.
// :visible / :hidden pseudo-classes
$('.box').is(':visible') // boolean
$('.box:visible').doSomething()
// note: visibility:hidden is now :hidden in jQuery 3+
// (display:none, opacity:0, width:0/height:0 are also :hidden)
// is element in viewport?
function inViewport($el) {
const elTop = $el.offset().top
const elBottom = elTop + $el.outerHeight()
const docTop = $(window).scrollTop()
const docBottom = docTop + $(window).height()
return elBottom >= docTop && elTop <= docBottom
}
// scroll-triggered reveal
$(window).on('scroll', function () {
$('.reveal').each(function () {
if (inViewport($(this))) {
$(this).addClass('shown')
}
})
})Scroll Position & Animation
Smooth scrolling: animate scrollTop on both html and body (cross-browser). Subtract header height for fixed navs. Infinite scroll: when scroll position + viewport height is within N pixels of document height, load more content. Parallax: translate elements based on scroll position. Always throttle scroll handlers (they fire dozens of times per second) — use _.throttle, a timer-based debounce, or requestAnimationFrame. Modern alternatives: CSS scroll-behavior:smooth for scrolling, IntersectionObserver for triggers.
// current scroll position
const scrollTop = $(window).scrollTop()
// smooth scroll to top
$('html, body').animate({ scrollTop: 0 }, 500)
// smooth scroll to element
function scrollTo($el, duration) {
const top = $el.offset().top - 20 // 20px offset for fixed header
$('html, body').animate({ scrollTop: top }, duration || 500)
}
scrollTo($('#section2'))
// infinite scroll: load more when near bottom
$(window).on('scroll', function () {
if ($(window).scrollTop() + $(window).height() >= $(document).height() - 100) {
loadMore()
}
})
// parallax (simple)
$(window).on('scroll', function () {
const y = $(window).scrollTop()
$('.hero').css('transform', 'translateY(' + (y * 0.5) + 'px)')
})Deferred & Promises
Creating a Deferred
$.Deferred is jQuery's promise implementation (pre-dates native Promise). Create one, then call .resolve(value) or .reject(reason) to settle it. .promise() returns a read-only view (consumers can't resolve it). The legacy API uses .done/.fail/.always instead of .then/.catch. jQuery 3+ Deferreds are thenable, so await and Promise.resolve work with them. Prefer native Promise for new code — Deferred is mainly relevant for AJAX and legacy APIs.
// create a Deferred (jQuery's promise source)
const deferred = $.Deferred()
// resolve or reject it later
setTimeout(function () {
if (Math.random() > 0.5) {
deferred.resolve('success data')
} else {
deferred.reject(new Error('failed'))
}
}, 1000)
// get the promise (read-only view)
const promise = deferred.promise()
// consume it
promise
.done(function (data) { console.log('done:', data) })
.fail(function (err) { console.log('fail:', err) })
.always(function () { console.log('settled') })When (Parallel Async)
$.when(deferreds...) waits for multiple Deferreds/Promises to all resolve, then calls .done with arrays of each result. If any rejects, .fail fires immediately. This is the parallel-async pattern (like Promise.all). The callback receives one array per input deferred (each containing [data, status, xhr] for AJAX). For modern code, Promise.all + fetch is cleaner and returns resolved values directly. $.when is still useful in jQuery-heavy codebases.
// run multiple async ops in parallel, wait for all
$.when(
$.get('/api/users'),
$.get('/api/posts'),
$.get('/api/comments')
).done(function (usersResp, postsResp, commentsResp) {
// each arg is [data, status, xhr]
const users = usersResp[0]
const posts = postsResp[0]
console.log('all loaded:', users, posts)
}).fail(function (err) {
console.error('one failed:', err)
})
// modern equivalent
Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json())
]).then(([users, posts]) => {
console.log('all loaded:', users, posts)
})Chaining .then()
jQuery 3+ aligned .then with the Promises/A+ spec — it takes (onFulfilled, onRejected) and returns a new promise, just like native Promise.then. You can chain async operations and transform values. Returning a promise from .then waits for it; returning a value passes it downstream. .catch is also supported in 3+. For jQuery 1.x/2.x, .then had non-standard behavior — use .pipe instead. If possible, upgrade to 3+ and use .then/.catch for cleaner async code.
// jQuery 3+: .then follows Promises/A+ (like native then)
$.get('/api/users')
.then(function (users) {
// transform or chain another async
return $.get('/api/users/' + users[0].id)
})
.then(function (userDetail) {
console.log('detail:', userDetail)
return userDetail.name // pass value downstream
})
.then(function (name) {
console.log('name:', name)
})
.catch(function (err) {
console.error('any error in chain:', err)
})
// in jQuery 1.x/2.x, .then had different semantics
// use .done/.pipe for old code; .then for newAJAX Promise Interface
All $.ajax calls (and $.get/$.post) return a jqXHR object, which is promise-like (supports .done/.fail/.always) and (in jQuery 3+) thenable (so await and Promise.resolve work). Multiple .done callbacks can be attached — they all fire on success. .abort() cancels the request (triggers .fail with status 'abort'). For new code, await $.ajax(...) or wrap with Promise.resolve() to integrate with native Promise chains and async/await cleanly.
// $.ajax returns a jqXHR (promise-like)
const req = $.ajax({ url: '/api/data', method: 'GET' })
// multiple callbacks on same request
req.done(function (data) { console.log('handler 1', data) })
req.done(function (data) { console.log('handler 2', data) })
req.fail(function (xhr, status, err) { console.error(err) })
req.always(function () { console.log('settled') })
// abort an in-flight request
req.abort()
// convert to native Promise (jQuery 3+)
Promise.resolve(req).then(function (data) {
console.log('via native promise:', data)
})
// use with async/await (jQuery 3+)
async function getData() {
try {
const data = await $.ajax({ url: '/api/data' })
console.log(data)
} catch (err) {
console.error(err)
}
}Custom Promise Pipeline
Wrap any async operation in a Deferred to get a promise: create, resolve/reject later, return .promise(). jQuery collections also expose .promise() which resolves when all queued animations on the elements complete — useful for triggering code after a batch of animations. This bridges jQuery's animation queue with promise-based code, letting you await animations or chain them with AJAX or other async work cleanly.
// wrap a callback-based API in a promise
function delay(ms) {
const d = $.Deferred()
setTimeout(function () { d.resolve() }, ms)
return d.promise()
}
// use it
delay(500).then(function () { console.log('500ms passed') })
// animate then continue
$('.box').fadeOut(300).promise().done(function () {
console.log('fade done')
})
// queue of animations on multiple elements
$('.box').each(function (i) {
$(this).delay(i * 100).fadeIn().promise()
})
// when animations on a set are done
$('.box').fadeOut().promise().done(function () {
console.log('all boxes faded')
})Forms
Form Events
submit fires on form submission — always preventDefault for AJAX. input fires on every keystroke (text) or value change — great for live validation or search-as-you-type. change fires on blur for text inputs but immediately for checkboxes, radios, and selects. focus/blur fire on field focus. Use event delegation on the form for dynamically added fields. The 'change' vs 'input' distinction matters: 'input' is live, 'change' is when the user finishes editing.
// submit event
$('form').on('submit', function (e) {
e.preventDefault() // stop page reload
const data = $(this).serialize()
$.post('/api/save', data)
})
// input event (fires on every keystroke/change)
$('input').on('input', function () {
console.log('current value:', $(this).val())
})
// change event (fires on blur for text, immediately for checkbox/select)
$('select').on('change', function () {
console.log('selected:', $(this).val())
})
// focus / blur
$('input').on('focus', function () { $(this).addClass('focused') })
$('input').on('blur', function () { $(this).removeClass('focused') })
// delegated (works for dynamically added fields)
$('form').on('input', '.field', function () { /* ... */ })Serializing Form Data
.serialize() produces a URL-encoded string ready for AJAX data. .serializeArray() gives a {name, value} array — convert to a plain object with reduce. Both respect the form's current state (checkboxes only included if checked; selects use the selected option). For checkboxes, the value is 'on' by default unless you set value='something'. Disabled fields are excluded. This is the standard way to gather form data for AJAX submission without a full reload.
<!-- form -->
<form id="f">
<input name="name" value="Alice">
<input name="email" value="[email protected]">
<select name="role"><option value="admin" selected>Admin</option></select>
<input type="checkbox" name="agree" checked>
</form>
<script>
// serialize: URL-encoded string
$('#f').serialize()
// 'name=Alice&email=a%40b.com&role=admin&agree=on'
// serializeArray: array of {name, value}
$('#f').serializeArray()
// [{ name: 'name', value: 'Alice' }, ...]
// convert to plain object
const obj = {}
$('#f').serializeArray().forEach(p => { obj[p.name] = p.value })
// { name: 'Alice', email: '[email protected]', role: 'admin', agree: 'on' }
// or one-liner reduce
const obj2 = $('#f').serializeArray().reduce((o, p) =>
(o[p.name] = p.value, o), {})
</script>Form Validation
Manual validation: iterate required fields, check .val(), toggle an 'invalid' class for CSS styling. Email regex is a basic sanity check (full RFC 5322 regex is overkill). For complex forms, use the jQuery Validation Plugin which provides declarative rules (class='required email') and consistent error messages. Always validate client-side for UX AND server-side for security — client validation can be bypassed. HTML5 attributes (required, type='email', pattern) are the modern baseline.
// simple validation
function validate($form) {
let valid = true
$form.find('[data-required]').each(function () {
const $f = $(this)
if (!$f.val()) {
$f.addClass('invalid')
valid = false
} else {
$f.removeClass('invalid')
}
})
$form.find('[type=email]').each(function () {
if (!/^[^@]+@[^@]+\.[^@]+$/.test($(this).val())) {
$(this).addClass('invalid')
valid = false
}
})
return valid
}
$('form').on('submit', function (e) {
e.preventDefault()
if (validate($(this))) {
$.post('/api/save', $(this).serialize())
}
})Dynamic Form Fields
Add/remove form fields dynamically by creating elements with $() and appending. Use array-style names (tags[]) so the server receives an array. Event delegation (on('click', '.remove', ...)) handles clicks on dynamically added remove buttons. For re-orderable fields (with up/down buttons or drag-drop), re-number the names after each change so the server sees a clean sequence. This pattern is common for tag inputs, dynamic line items, and configuration forms.
// add a new field
$('.add-field').on('click', function () {
const $field = $('<input>', {
type: 'text',
name: 'tags[]',
class: 'tag-input',
placeholder: 'Tag'
})
$('#fields').append($field)
})
// remove a field
$('#fields').on('click', '.remove', function () {
$(this).closest('.field-row').remove()
})
// array field names (tags[]) — server receives an array
// PHP/Rails auto-parse 'name[]' into arrays
// re-number fields after add/remove
function renumber() {
$('#fields .field').each(function (i) {
$(this).find('input').attr('name', 'item_' + i)
})
}AJAX Form Submission
A reusable AJAX form handler: serialize the form, POST to its action URL, handle success/error. Disable the submit button during the request to prevent double-submits. Trigger custom events (ajax:success, ajax:error) so other code can react. Reset the form on success. Show server validation errors by mapping field names to error messages. This pattern turns any form into an AJAX form by adding class='ajax' — a common enhancement for progressive enhancement.
// generic AJAX form submission
$('form.ajax').on('submit', function (e) {
e.preventDefault()
const $form = $(this)
const data = $form.serialize()
const url = $form.attr('action') || window.location.href
const method = $form.attr('method') || 'POST'
// show loading state
$form.find('button').prop('disabled', true).addClass('loading')
$.ajax({
url: url,
method: method,
data: data,
dataType: 'json'
})
.done(function (resp) {
$form.trigger('ajax:success', [resp])
$form[0].reset()
})
.fail(function (xhr) {
$form.trigger('ajax:error', [xhr])
const errors = xhr.responseJSON?.errors
if (errors) showErrors($form, errors)
})
.always(function () {
$form.find('button').prop('disabled', false).removeClass('loading')
})
})Iteration & Loops
$.each vs .each()
$.each is a generic utility that iterates arrays (index, value) or plain objects (key, value). .each() is a method on jQuery collections — 'this' is the raw DOM element. Both accept return false to break and anything else to continue. Note the arg order is (index, value) — opposite of Array.forEach's (value, index). For modern code, native forEach/map/for...of is preferred; .each() remains useful when working with jQuery collections and you need the element as 'this'.
// $.each: generic iterator (arrays AND objects)
$.each([10, 20, 30], function (index, value) {
console.log(index, value) // 0 10, 1 20, 2 30
})
$.each({ a: 1, b: 2 }, function (key, value) {
console.log(key, value) // 'a' 1, 'b' 2
})
// .each(): method on jQuery collections only
$('li').each(function (index, element) {
// 'this' is the DOM element (= element)
// wrap with $(this) for jQuery methods
$(this).text(index + ': ' + $(this).text())
})
// breaking out
$('li').each(function (i) {
if (i === 3) return false // stop iteration
// return true (or nothing) to continue
})$.map vs .map()
$.map transforms an array/object into a new array (return null to skip, return an array to flatten). Note arg order is (value, index) — opposite of $.each. .map() on jQuery collections returns a new jQuery collection (call .get() to convert to a plain array). Returning null/undefined from .map() excludes that item. These pre-date native Array.map; modern code uses native map, but jQuery's .map() is still handy for extracting values from DOM collections.
// $.map: transform an array or object
const doubled = $.map([1, 2, 3], function (value, index) {
return value * 2 // [2, 4, 6]
})
// returning null/undefined removes the item
const evens = $.map([1, 2, 3, 4], function (v) {
return v % 2 === 0 ? v : null // [2, 4]
})
// returning an array flattens it
const flat = $.map([1, 2], function (v) {
return [v, v * 10] // [1, 10, 2, 20]
})
// .map() on jQuery collections
const ids = $('li').map(function (index, el) {
return $(el).data('id') // jQuery collection of ids
}).get() // .get() converts to plain array [1, 2, 3]Looping with for/while
For raw performance with large collections, use a classic for loop with $items[i] (raw DOM element) instead of $items.eq(i) (jQuery-wrapped) — avoids creating a new jQuery object per iteration. The .length property works directly. for...of works on jQuery collections (they're iterable). However, prefer .each() for readability unless you've measured a performance issue — the difference is rarely noticeable except on thousands of elements.
// classic for loop with .eq(i)
const $items = $('li')
for (let i = 0; i < $items.length; i++) {
const $item = $items.eq(i)
$item.text('Item ' + i)
}
// or use the raw DOM elements (faster, no jQuery wrapping)
for (let i = 0; i < $items.length; i++) {
$items[i].textContent = 'Item ' + i // raw DOM
}
// while loop
let i = 0
while (i < $items.length) {
$($items[i]).addClass('item-' + i)
i++
}
// for...of (modern, on the collection)
for (const el of $items) {
$(el).addClass('processed')
}Filtering During Iteration
Filter before iterating for cleaner code — $('li').filter('.active').each(...) is more idiomatic than conditionals inside .each(). To collect a subset, push matching raw DOM elements into an array and wrap with $() at the end. For value extraction, .map().get() is the cleanest pattern. Avoid building jQuery collections in loops (each $() call has overhead) — collect raw elements, wrap once.
// filter then iterate
$('li').filter('.active').each(function () {
$(this).addClass('highlighted')
})
// iterate with conditional logic
$('li').each(function (i) {
if (i % 2 === 0) {
$(this).addClass('even')
} else {
$(this).addClass('odd')
}
})
// collect matching items
const active = []
$('li').each(function () {
if ($(this).hasClass('active')) {
active.push(this) // push raw DOM element
}
})
const $active = $(active) // wrap in jQuery
// or use .map().get()
const ids = $('li.active').map(function () {
return $(this).data('id')
}).get()Nesting & Complex Iteration
Nested .each() handles grids/matrices. For building DOM from data, append to a document fragment and insert once (much faster than appending per item — each append triggers reflow). $(document.createDocumentFragment()) creates a jQuery-wrapped fragment. For very large lists, consider string concatenation + .html() (fastest) or a templating library. Modern frameworks (Vue/React) handle this declaratively, but the fragment pattern remains a useful jQuery optimization.
// nested iteration over rows and cells
$('table tr').each(function (rowIdx, row) {
$(row).find('td').each(function (colIdx, cell) {
$(cell).text('R' + rowIdx + 'C' + colIdx)
})
})
// iterate over a JSON response and build DOM
$.get('/api/users').done(function (users) {
const $list = $('#users')
$.each(users, function (i, user) {
$list.append(
$('<li>').text(user.name).data('id', user.id)
)
})
})
// build with a document fragment (faster)
const $frag = $(document.createDocumentFragment())
$.each(users, function (i, user) {
$frag.append('<li>' + user.name + '</li>')
})
$('#users').append($frag) // one DOM updateData Storage & Caching
$.data vs .data()
$.data(el, key, val) is the low-level function on a raw DOM element; .data() is the convenient method on jQuery collections. Both store values in an internal cache (not in the DOM), so it's faster than attribute access. .data() also auto-reads data-* attributes with type conversion (numbers, booleans, JSON). Setting via .data(key, val) does NOT update the data-* attribute — use .attr('data-key', val) for that. Use $.removeData(el, key) or .removeData(key) to clear.
// $.data: low-level, on raw DOM element
const el = document.getElementById('box')
$.data(el, 'count', 1)
$.data(el, 'count') // 1
$.data(el, 'count', 2)
$.data(el, 'count') // 2
$.removeData(el, 'count')
// .data(): method on jQuery collection (more common)
$('.box').data('count', 1)
$('.box').data('count') // 1
$('.box').removeData('count')
// auto-reads data-* attributes (with type conversion)
// <div data-id="42" data-active="true"></div>
$('.box').data('id') // 42 (number)
$('.box').data('active') // true (boolean)Storing Per-Element State
Per-element state is the killer feature of .data() — store config, counters, flags, or objects associated with each element without polluting the DOM. Calling .data() with no args returns all stored data as an object. Note that storing an object and mutating it mutates the stored reference (no need to re-set). The internal cache is auto-cleaned when elements are removed via .remove(), preventing memory leaks — but not if you remove them via raw DOM methods (parentNode.removeChild).
// store per-element state on plugin init
$('.counter').each(function () {
$(this).data('count', 0)
})
// increment on click
$('.counter').on('click', function () {
const $el = $(this)
const c = $el.data('count') + 1
$el.data('count', c)
$el.text('Count: ' + c)
})
// retrieve all data
const all = $('.counter').data() // { count: 5, ... }
// store objects
$('.box').data('config', { color: 'red', size: 10 })
$('.box').data('config').color = 'blue' // mutates the stored object
$('.box').data('config').color // 'blue'Caching Selections
Caching jQuery selections avoids re-querying the DOM — a significant perf win in loops or frequent handlers. Convention: prefix cached jQuery objects with $ ($boxes, $nav) to distinguish from raw DOM elements. Beware: cached selections become stale if the DOM changes (you add/remove matching elements). Re-query after dynamic content insertion. For event handlers, caching outside the handler (in the same scope) avoids re-querying on every event.
// BAD: re-querying the DOM on every use
function update() {
$('.box').text('updated') // queries every call
$('.box').addClass('done') // queries again
}
// GOOD: cache the selection
const $boxes = $('.box') // query once
function update() {
$boxes.text('updated')
$boxes.addClass('done')
}
// re-query when content changes
$('#container').load('/partial', function () {
// $boxes is stale — re-query
const $newBoxes = $('.box')
})
// convention: prefix cached jQuery objects with $
const $nav = $('#nav')
const $items = $('.item')HTML5 data-* Attributes
data-* attributes let you embed config in HTML declaratively — perfect for progressive enhancement (server renders config, JS reads it). .data() reads with type conversion (numbers, booleans, JSON objects); .attr() reads the raw string. Multi-word names use kebab-case in HTML (data-foo-bar) and camelCase in JS (.data('fooBar')). For JSON values, use single quotes in HTML to wrap the JSON (which uses double quotes). Updating data-* via .attr() persists to DOM; .data() writes to cache only.
<!-- declarative config in HTML -->
<div class="widget"
data-id="42"
data-mode="advanced"
data-config='{"timeout":5000,"retries":3}'
data-enabled="true">
Widget
</div>
<script>
// .data() reads with type conversion
const $w = $('.widget')
$w.data('id') // 42 (number)
$w.data('mode') // 'advanced'
$w.data('config') // { timeout: 5000, retries: 3 } (parsed JSON)
$w.data('enabled') // true (boolean)
// .attr() reads raw strings
$w.attr('data-id') // '42' (string)
$w.attr('data-config') // '{"timeout":5000,...}' (string)
// camelCase mapping: data-foo-bar -> .data('fooBar')
$('[data-foo-bar]').data('fooBar')
</script>Memory & Cleanup
Use .remove() to delete elements — it cleans up associated data and events automatically. .detach() removes from DOM but preserves data/events (for re-insertion). .empty() removes children AND cleans their data/events. NEVER use .html('') to clear children — it doesn't clean up their data/events, causing memory leaks. For plugins, expose a destroy method that unbinds events and removes data. jQuery's auto-cleanup is a major reason to use .remove() rather than raw DOM removal.
// .remove() cleans up data and events
$('.box').remove() // safe — data and events gone
// .detach() preserves data and events for re-insertion
const $box = $('.box').detach()
$('#elsewhere').append($box) // events still fire
// .empty() removes children AND their data/events
$('.container').empty() // children's data/events cleaned
// .html('') does NOT clean up children's data/events (leak!)
// use .empty() instead
// manual cleanup before removal
$('.box').off().removeData().remove()
// custom cleanup in a plugin
$.fn.myPlugin = function () {
this.on('click.myplugin', handler)
this.data('myplugin', { /* state */ })
// expose destroy for manual cleanup
// ... or use .on('remove') event (non-standard)Custom Animations
.animate() Basics
.animate() tweens numeric CSS properties over a duration. Use relative values ('+=50', '-=20') for incremental changes. Property names must be camelCase (marginLeft, not margin-left). Special values 'toggle', 'show', 'hide' work like their method counterparts. Numbers default to pixels. Colors require the jQuery Color plugin — core jQuery only animates numbers. The callback fires once when the animation completes (per element, so may fire multiple times for collections).
// animate numeric CSS properties
$('.box').animate({
width: '500px',
height: 200, // number = px
opacity: 0.5,
marginLeft: '+=50', // relative
fontSize: '20px' // camelCase for hyphenated props
}, 1000) // duration in ms
// with callback
$('.box').animate({ left: 300 }, 1000, function () {
console.log('done')
})
// 'slow' = 600, 'fast' = 200
$('.box').animate({ height: 'toggle' }, 'slow')
// special values: 'toggle', 'show', 'hide'
$('.box').animate({ width: 'toggle' })Easing & Options
Easing controls the speed curve: 'swing' (default, slow start/end, fast middle) and 'linear' (constant). jQuery UI or the easing plugin add 30+ more (easeInOutCubic, easeOutBounce). The options form gives fine control: step is called every animation frame (useful for syncing other elements), queue:false runs in parallel (default true queues sequentially), specialEasing sets per-property easing. step receives (now, fx) where fx has fx.prop (property name) and fx.start/fx.end.
// easing: 'swing' (default, ease-out) or 'linear'
$('.box').animate({ left: 300 }, 1000, 'linear')
// options object form
$('.box').animate({ left: 300 }, {
duration: 1000,
easing: 'swing',
complete: function () { console.log('done') },
step: function (now, fx) {
console.log(fx.prop, now) // called each frame
},
queue: true, // default true (sequential)
specialEasing: {
width: 'linear', // per-property easing
height: 'swing'
}
})
// jQuery UI adds more easings: easeInOutCubic, etc.
// or include jquery.easing pluginAnimation Queue
Each element has a 'fx' queue — animations on the same element run sequentially by default. Use queue:false to run in parallel. .delay(ms) inserts a pause. .queue(fn) inserts a custom function into the queue — call next() (or dequeue()) to continue. This lets you mix animations with arbitrary code (AJAX, callbacks) in a sequence. .clearQueue() empties the queue without affecting the current animation. .dequeue() advances to the next queued item.
// animations queue per element (default)
$('.box')
.animate({ width: 500 }, 500) // runs first
.animate({ height: 300 }, 500) // runs after
.animate({ opacity: 0.5 }, 500) // runs after that
// total: 1500ms (sequential)
// run in parallel: queue: false
$('.box').animate({ width: 500 }, { duration: 500, queue: false })
$('.box').animate({ height: 300 }, { duration: 500, queue: false })
// both run simultaneously
// .delay(): pause in the queue
$('.box')
.animate({ width: 500 }, 500)
.delay(300) // wait 300ms
.animate({ height: 300 }, 500)
// .queue(): insert custom function into the queue
$('.box').animate({ width: 500 }, 500).queue(function (next) {
console.log('between animations')
next() // must call to continue
})Color Animations
Core jQuery can't animate colors (they're non-numeric). The jQuery Color plugin (small, standalone) or jQuery UI adds color animation support — backgroundColor, color, borderColor, etc. jQuery UI also enables class-based animations: .addClass('foo', 500) transitions to the new class's styles over 500ms. For modern sites, prefer CSS transitions (transition: background 0.3s) — they're hardware-accelerated and don't require JS. Use jQuery color animation only for legacy support.
<!-- jQuery core does NOT animate colors -->
<!-- include jQuery Color plugin or jQuery UI -->
<script src="https://code.jquery.com/color/jquery.color-2.2.0.min.js"></script>
<script>
// now color animations work
$('.box').animate({
backgroundColor: '#ff0000',
color: '#ffffff',
borderColor: '#000000'
}, 1000)
// jQuery UI also enables:
// - class animations: .toggleClass('active', 500)
// - more easing functions
// - color transitions on hover
$('.box').hover(function () {
$(this).animate({ backgroundColor: '#eee' }, 300)
}, function () {
$(this).animate({ backgroundColor: '#fff' }, 300)
})
</script>Stopping & Clearing
.stop() halts the current animation. With (clearQueue=true), it also empties the queue. With (jumpToEnd=true), it jumps the current animation to its final state. .finish() jumps all queued animations to their final states instantly. The :animated selector matches elements mid-animation. Always pair hover-driven animations with .stop(true) to prevent buildup when the mouse enters/leaves rapidly — without it, animations queue up and play out long after the mouse leaves, causing janky behavior.
// .stop([clearQueue], [jumpToEnd])
$('.box').stop() // stop current, stay at current state
$('.box').stop(true) // also clear the queue
$('.box').stop(true, true) // clear queue AND jump current to end
$('.box').stop(false, true) // jump current to end, keep queue
// .finish(): jump ALL queued animations to end state
$('.box').finish()
// prevent hover animation buildup
$('.box').hover(function () {
$(this).stop(true).animate({ width: 300 }, 200)
}, function () {
$(this).stop(true).animate({ width: 100 }, 200)
})
// check if currently animating
if (!$(':animated').length) { /* nothing animating */ }
if ($('.box').is(':animated')) { /* still animating */ }Event Delegation
Why Delegation?
Event delegation binds one handler to a parent; clicks on children bubble up and trigger it. Benefits: (1) one handler instead of N (memory), (2) automatically works for dynamically added elements, (3) faster setup (one bind vs N). The second arg to .on() ('li') is the selector filter — 'this' inside the handler is the matched child, not the parent. Use delegation for any list, table, or container where children share behavior. The only cost: slight overhead per event (selector check), which is negligible.
// ANTI-PATTERN: bind to each <li> directly
$('li').on('click', function () {
$(this).toggleClass('done')
})
// - 1 handler PER <li> (100 items = 100 handlers)
// - NEW <li> added later DON'T get the handler
// - high memory, slow setup
// PATTERN: delegate to parent
$('ul').on('click', 'li', function (e) {
$(this).toggleClass('done')
})
// - 1 handler total
// - works for future <li>
// - low memory, fast setup
// - 'this' is the <li> (not the <ul>)Delegation in Practice
This todo-list pattern shows delegation's power: two delegated handlers on #tasks handle delete buttons and item toggling for all current and future items. Adding a new <li> requires no re-binding. e.stopPropagation() on the delete button prevents the li click from also firing (which would toggle .done when deleting). .closest('li') finds the enclosing <li> from the clicked button. This pattern is the foundation of jQuery SPAs before frameworks — and still useful for widget-like UI.
<!-- dynamic list -->
<ul id="tasks">
<li>Task 1 <button class="delete">×</button></li>
</ul>
<script>
// ONE delegated handler handles all current AND future items
$('#tasks').on('click', '.delete', function (e) {
e.stopPropagation()
$(this).closest('li').remove()
})
$('#tasks').on('click', 'li', function () {
$(this).toggleClass('done')
})
// adding new items works without re-binding
$('.add').on('click', function () {
$('#tasks').append('<li>New task <button class="delete">×</button></li>')
})
</script>Delegated vs Direct Binding
Direct binding attaches the handler to the matched elements; delegated binding attaches to a parent and filters by selector. Key difference inside the handler: for direct, e.currentTarget === this === the element. For delegated, e.currentTarget is the parent (where the handler is bound), this is the matched child (the selector match), and e.target is the actual clicked element (may be a descendant of the matched child). Use e.delegateTarget to access the parent in delegated handlers.
// DIRECT: bind directly to element
// - element must exist at bind time
// - handler attached to element itself
// - e.target === e.currentTarget === this
$('.btn').on('click', function (e) {
console.log(e.currentTarget === this) // true
})
// DELEGATED: bind to parent, filter by selector
// - works for future children
// - handler attached to parent
// - e.currentTarget = parent (the bound element)
// - this = child (matched selector)
// - e.target = actual clicked element (may be deeper child)
$('ul').on('click', 'li', function (e) {
console.log(e.currentTarget) // the <ul>
console.log(this) // the <li>
console.log(e.target) // clicked element (maybe <span> in li)
})Unbinding Delegated Handlers
Namespacing (click.task) is the cleanest way to unbind specific handlers without affecting others. .off('click', 'li') removes delegated handlers matching that selector. .off('click') removes all click handlers (direct and delegated). .off() removes everything — use carefully. To remove a specific handler, pass the original function reference. For plugins, always namespace events (click.myplugin) so users can cleanly unbind without knowing your internals.
// delegate with namespace
$('ul').on('click.task', 'li', handler)
// remove just this delegated handler (by namespace)
$('ul').off('click.task')
// remove all delegated click handlers
$('ul').off('click')
// remove all delegated handlers with a specific selector
$('ul').off('click', 'li')
// remove ALL handlers (including delegated)
$('ul').off()
// remove with reference to the original handler
function handler() { /* ... */ }
$('ul').on('click', 'li', handler)
$('ul').off('click', 'li', handler) // exact matchDelegation Pitfalls
Scope delegation to the nearest stable ancestor — don't delegate to document/body for everything (every event bubbles up and is checked against the selector, which is slow). Avoid overly broad selectors ('div'). Be careful with stopPropagation in children: it prevents delegated handlers on ancestors from firing. If a child stops propagation, the parent's delegated handler never runs. The trade-off: delegation is flexible but adds a tiny per-event overhead (selector matching) — usually negligible, but matters on huge pages.
// PITFALL 1: delegated handler on a parent that's too high
$(document).on('click', '.btn', handler)
// works, but EVERY click bubbles to document and is checked
// (slow on large pages — scope to nearest stable parent)
// BETTER: scope to the nearest container that won't be replaced
$('#sidebar').on('click', '.btn', handler)
// PITFALL 2: selector that matches too broadly
$('body').on('click', 'div', handler) // fires on every div click
// PITFALL 3: stopping propagation in a child breaks delegation
$('.btn').on('click', function (e) {
e.stopPropagation() // parent's delegated handler won't fire!
})
// PITFALL 4: delegated handlers don't fire if a parent
// stops propagation before reaching the delegate targetBest Practices
Performance Tips
Key performance tips: (1) cache selections; (2) scope queries to a container with .find(); (3) prefer #id selectors (use getElementById internally); (4) avoid *; (5) delegate events; (6) batch DOM updates via fragments — each append triggers reflow, so build a fragment and append once; (7) detach before heavy manipulation (operations on detached elements don't trigger reflow). For 1000+ item lists, string concatenation + .html() is fastest, but fragment is cleaner.
// 1. cache selections
const $items = $('.items') // query once
// 2. scope queries to a container
$('.item', $container) // search within container
$container.find('.item') // equivalent, slightly faster
// 3. use ID selectors when possible (fastest)
$('#header') // getElementById
// 4. avoid universal selector
$('*').doSomething() // SLOW
// 5. delegate events instead of binding many
$('ul').on('click', 'li', fn) // 1 handler
// 6. batch DOM updates (use document fragment)
const $frag = $(document.createDocumentFragment())
for (let i = 0; i < 1000; i++) {
$frag.append('<li>Item ' + i + '</li>')
}
$('ul').append($frag) // one reflow, not 1000
// 7. detach before heavy manipulation
const $list = $('ul').detach()
// ... modify $list ...
$('#container').append($list)DOM Insertion Performance
DOM insertion is the biggest perf bottleneck. Each .append() triggers reflow (the browser recalculates layout). For bulk inserts: (1) build an HTML string and set with .html() (fastest for simple content); (2) use a document fragment and append once (cleaner for complex elements); (3) detach the container, modify, re-attach (avoids reflow on the live DOM during modification). For 1000+ items, the string approach is significantly faster than per-item .append().
// SLOW: appending in a loop (reflow per iteration)
for (let i = 0; i < 1000; i++) {
$('ul').append('<li>Item ' + i + '</li>')
}
// FASTER: build string, append once
let html = ''
for (let i = 0; i < 1000; i++) {
html += '<li>Item ' + i + '</li>'
}
$('ul').html(html)
// FASTEST: detached node + fragment
const $list = $('ul').detach()
const $frag = $(document.createDocumentFragment())
for (let i = 0; i < 1000; i++) {
$frag.append($('<li>').text('Item ' + i))
}
$list.append($frag).appendTo('#container')
// even faster: array.join
const html = new Array(1000).fill(0).map((_, i) =>
'<li>Item ' + i + '</li>'
).join('')
$('ul').html(html)Modern jQuery Patterns
Modern jQuery (3.x) patterns: use .on()/.off() (legacy methods like .bind/.live/.delegate are removed or deprecated). Use the promise interface (.done/.fail) over success/error callbacks. Scope code in IIFEs or ES modules — don't pollute globals. Skip $(document).ready if your script is at the end of body. Prefer CSS transitions/animations over .animate() (hardware-accelerated, smoother). Use 'use strict'. For new projects, evaluate whether a framework (Vue/React) is more appropriate than jQuery.
// use .on() / .off() (not bind/unbind, live, delegate)
$('.btn').on('click', fn)
// use promises (.done/.fail) instead of success/error
$.get('/api').done(fn).fail(errFn)
// use IIFE or modules to scope code
(function ($) {
'use strict'
// private code
$('.btn').on('click', fn)
}(jQuery))
// or ES modules
import $ from 'jquery'
$('.btn').on('click', fn)
// don't pollute global scope
// don't use $(document).ready if script is at end of body
// prefer CSS for animations (hardware-accelerated)
// .css({ transition: 'all 0.3s', transform: 'translateX(100px)' })Accessibility
Accessibility essentials: add ARIA roles and states (role='dialog', aria-modal, aria-expanded, aria-labelledby). Make custom widgets keyboard-accessible — handle Enter/Space for buttons, arrow keys for menus, Esc to close modals. Manage focus: move focus into modals when opened, return focus to the trigger when closed. Use semantic elements (<button>, <a>, <nav>) rather than divs with onclick. Visibility: prefer hiding via CSS classes (which screen readers may ignore) over display:none (which they always ignore).
// add ARIA attributes for screen readers
$('.modal').attr({
role: 'dialog',
'aria-modal': true,
'aria-labelledby': 'modal-title'
})
// toggle aria-expanded for collapsibles
$('.accordion .header').on('click', function () {
const $h = $(this)
const expanded = $h.attr('aria-expanded') !== 'true'
$h.attr('aria-expanded', expanded)
$h.next('.content').slideToggle()
})
// keyboard navigation
$('.menu-item').on('keydown', function (e) {
if (e.which === 13 || e.which === 32) { // Enter or Space
$(this).trigger('click')
}
})
// focus management for modals
$('.modal').on('shown', function () {
$(this).find('input:first').focus()
})
// use button elements for actions (not <div onclick>)
$('<button>').text('Save').on('click', save).appendTo('body')Security: XSS & HTML Injection
Never inject untrusted strings via .html() — it executes embedded <script> tags and event handlers (XSS). Use .text() for plain text (auto-escaped). When you must include HTML, sanitize first (manual escaping or a library like DOMPurify). The safest pattern: build elements with $() and use .text() for dynamic content — $('<li>').text(userInput) escapes automatically. Be especially careful with .append('<div>' + data + '</div>') patterns — these are XSS vectors if data is user-controlled.
// DANGEROUS: injecting untrusted HTML
const userInput = '<script>alert("xss")</script>'
$('.box').html(userInput) // EXECUTES the script!
// SAFE: escape with .text()
$('.box').text(userInput) // displays as text, no execution
// SAFE: sanitize before injecting
function escapeHtml(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
$('.box').html(escapeHtml(userInput))
// SAFE: build elements with .text() for content
const $li = $('<li>').text(userInput) // text is auto-escaped
$('ul').append($li)
// AVOID .html() with user input. Use .text() or DOMPurify.
// $('div').html('<p>' + data + '</p>') // BAD if data is user inputCommon Patterns
Modal Dialog
A modal pattern: create overlay + modal, append to body (escape parent stacking contexts), fade in. Click on overlay (not modal) closes — use e.target === this to detect. Esc key closes. Always clean up (remove DOM, unbind handlers) on close. For production, consider a library (Magnific Popup, Bootstrap Modal) that handles accessibility (focus trapping, ARIA, return focus to trigger), animations, and iframe/ajax modals. The pattern above is the minimal hand-rolled version.
// simple modal pattern
function openModal(html) {
const $overlay = $('<div class="modal-overlay"></div>')
const $modal = $('<div class="modal"></div>').html(html)
$overlay.append($modal).appendTo('body').hide().fadeIn(200)
$overlay.on('click', function (e) {
if (e.target === this) closeModal() // click outside to close
})
$(document).on('keydown.modal', function (e) {
if (e.which === 27) closeModal() // Esc to close
})
}
function closeModal() {
$('.modal-overlay').fadeOut(200, function () { $(this).remove() })
$(document).off('keydown.modal')
}
// usage
$('.open').on('click', function () {
openModal('<p>Hello from modal!</p>')
})Tabs
Classic tab pattern: clicking a tab link activates that tab. The link's href (#tab1) maps to the content's id. Active class on both the link and content controls visibility (CSS: .tab-content:not(.active) { display: none }). The handler uses event delegation (handles dynamically added tabs).preventDefault stops the URL hash from changing (or omit preventDefault to enable hash-based deep-linking to tabs). For accessibility, add role='tab', aria-selected, and keyboard arrow navigation.
<!-- HTML -->
<div class="tabs">
<ul class="tab-nav">
<li><a href="#tab1" class="active">Tab 1</a></li>
<li><a href="#tab2">Tab 2</a></li>
<li><a href="#tab3">Tab 3</a></li>
</ul>
<div id="tab1" class="tab-content active">Content 1</div>
<div id="tab2" class="tab-content">Content 2</div>
<div id="tab3" class="tab-content">Content 3</div>
</div>
<script>
$('.tab-nav').on('click', 'a', function (e) {
e.preventDefault()
const $a = $(this)
$a.addClass('active').parent().siblings().find('a').removeClass('active')
$($a.attr('href')).addClass('active').siblings().removeClass('active')
})
</script>Accordion
Accordion patterns: clicking a header toggles its content. Single-open variant closes others when one opens (remove the .not($h) block for multi-open). Use aria-expanded to expose state to screen readers — CSS can style based on it (.acc-header[aria-expanded="true"] { ... }). slideToggle animates the content. Use <button> for headers (keyboard-accessible by default). For many items, event delegation on the parent avoids binding each header individually.
<!-- HTML -->
<div class="accordion">
<div class="acc-item">
<button class="acc-header" aria-expanded="false">Section 1</button>
<div class="acc-content">Content 1</div>
</div>
<!-- more items -->
</div>
<script>
$('.accordion').on('click', '.acc-header', function () {
const $h = $(this)
const $c = $h.next('.acc-content')
// close others (single-open accordion)
$h.closest('.accordion')
.find('.acc-header').not($h)
.attr('aria-expanded', false)
.next('.acc-content').slideUp()
// toggle this one
const isOpen = $h.attr('aria-expanded') === 'true'
$h.attr('aria-expanded', !isOpen)
$c.slideToggle()
})
</script>Infinite Scroll
Infinite scroll: detect when the user scrolls near the bottom, fetch more items, append them. Guard against concurrent loads (loading flag). When the server returns no more items, unbind the scroll handler (no more attempts). Append via a fragment for performance. Throttle the scroll handler (it fires many times per second) — either with a debounce/_.throttle or check the loading flag (as here) to skip while a request is in-flight. Modern alternative: IntersectionObserver on a sentinel element at the bottom.
let loading = false
let page = 1
function loadMore() {
if (loading) return
loading = true
$('.loader').show()
$.get('/api/items', { page: page })
.done(function (items) {
if (items.length === 0) {
// no more items — unbind the scroll handler
$(window).off('scroll.inf')
return
}
const $frag = $(document.createDocumentFragment())
items.forEach(item => {
$frag.append($('<li>').text(item.name))
})
$('ul.items').append($frag)
page++
})
.always(function () {
loading = false
$('.loader').hide()
})
}
// trigger when near bottom
$(window).on('scroll.inf', function () {
if ($(window).scrollTop() + $(window).height() > $(document).height() - 200) {
loadMore()
}
})Live Search (Debounced)
Live search: as the user types, query the server and show results. Debounce is essential — without it, every keystroke fires a request (rapid typing = dozens of requests). The debounce waits N ms after the last keystroke before firing. 300ms is a good default. Always handle the empty query (clear results). For production, also cancel in-flight requests when a new one starts (use .abort() on the previous jqXHR) to avoid out-of-order responses. Modern alternatives: AbortController with fetch.
// debounce: wait until user pauses typing
function debounce(fn, wait) {
let timer
return function () {
clearTimeout(timer)
const args = arguments
const ctx = this
timer = setTimeout(() => fn.apply(ctx, args), wait)
}
}
// live search
const $input = $('#search')
const $results = $('#results')
$input.on('input', debounce(function () {
const q = $(this).val()
if (!q) { $results.empty(); return }
$.get('/api/search', { q: q })
.done(function (items) {
$results.empty()
items.forEach(item => {
$results.append($('<li>').text(item.name))
})
})
}, 300)) // wait 300ms after last keystroke
// without debounce, a search fires on every keystroke (too many requests)Fragmentos de jQuery relacionados
Copy-paste ready code for common tasks.
Selectors
Select elements by id, class, attribute, and pseudo-filters.
Events & Delegation
Bind handlers with on(), support delegation and namespaced removal.
DOM Manipulation
Get/set text, html, attributes, classes, and insert nodes.
AJAX Requests
Use $.ajax, $.get, $.post, and load with promises.
Effects & Animation
Show, hide, fade, slide, and animate elements.
Traversing the DOM
Navigate relatives and filter the matched set.
Utility Methods
Iterate, merge, and filter with jQuery helpers.
Method Chaining
Chain jQuery methods and use end() to restore context.
Was this helpful?