Knowledge/jQuery/Introduction
26 Chapters
3.x Baseline
Living Document
Free License
Engineering Publication · Syed Omar Ibn Feroj← Back to portfolio
jQ
Engineering Notes · Open Knowledge Repository

jQuery — Engineering
Notes & the Way Off It

A working reference for jQuery in codebases that still run it — selectors, traversal, events and Ajax done correctly — paired with the native-DOM equivalent for every pattern, so you can maintain it today and migrate it tomorrow.

26
Chapters
3.x
Baseline
Living
Document
Free
License
Ch. 01
Setup & ready
Load it, then wait for the DOM.
1.1 — The ready gate
JavaScript
// run after the DOM is parsed
$(function () {
  // safe to query/manipulate here
});
// equivalent: $(document).ready(fn)
$(fn) waits for DOM-ready, not full load (images/CSS) — that's what you want for setup. If the script tag is already at the end of <body> with defer, you often don't even need it.
Ch. 02
Selectors
CSS selectors plus a few jQuery-only extensions.
2.1 — Select like CSS
JavaScript
$("#id"); $(".cls"); $("ul > li");
$("input[type=email]");
$("li:first"); $(":checked"); $(":visible");  // jQuery extensions
jQuery-only pseudo-selectors (:visible, :first, :contains) can't use the browser's fast native querySelectorAll path — jQuery filters in JS. On large pages prefer standard CSS selectors.
Ch. 03
The jQuery Object
A wrapped, array-like set — not a DOM node.
3.1 — Wrapped set vs raw node
JavaScript
const $els = $(".item");   // jQuery set (maybe 0..n)
$els.length;                  // how many matched
$els[0];                    // underlying DOM node
$els.get(0);                // same, bounds-safe
$(domNode);                   // wrap a node back up
A selector that matches nothing returns an empty set, not null — methods silently no-op. "Nothing happens" almost always means $(sel).length === 0; check it before assuming the method is broken.
Ch. 04
Traversal
Walk the tree from a set.
4.1 — Up, down, sideways
JavaScript
$(".x").closest(".card");   // nearest ancestor (incl self)
$(".x").find("a");          // descendants
$(".x").parent().children();
$(".x").next(); $(".x").siblings();
$(".x").filter(".active").end();  // end() pops the chain
closest() (not parent()) is what you want in delegated handlers — it finds the meaningful ancestor regardless of how deeply the click landed.
Ch. 05
Chaining
Most methods return the set — fluent by design.
5.1 — One statement, many ops
JavaScript
$(".card")
  .addClass("on")
  .css("opacity", 1)
  .find(".title").text("Hi").end()
  .fadeIn(200);
Getters (.text(), .val(), .attr(name) with no value) break the chain — they return a value, not the set. Only setters are chainable.
Ch. 06
Content
html, text, val — and the XSS line.
6.1 — Read/write content
JavaScript
$("#out").text(userInput);   // safe — escaped
$("#out").html(trustedMarkup); // parses HTML — danger if untrusted
$("#name").val();              // form value (get)
.html(x) with any user-controlled x is an XSS vector — identical risk to innerHTML. Use .text() for user data; reserve .html() for markup you generated.
Ch. 07
Attributes & props
The attr/prop distinction that confuses everyone.
7.1 — attr is the HTML, prop is the live state
JavaScript
$("#c").attr("checked");   // the initial HTML attribute
$("#c").prop("checked");   // the CURRENT checked state (bool)
For checked, disabled, selected, value use .prop().attr() returns the original HTML and won't reflect user interaction. Using attr("checked") for a live checkbox is a classic bug.
Ch. 08
Classes & CSS
toggleClass, and why inline css() is a smell.
8.1 — Prefer classes to inline styles
JavaScript
$(".x").addClass("active").removeClass("loading");
$(".x").toggleClass("open", isOpen);
$(".x").hasClass("active");
$(".x").css({ color: "red" });   // last resort
Toggle a class and keep styling in the stylesheet. Scattering .css() calls makes state un-inspectable and impossible to theme; a single class flips dozens of declarations.
Ch. 09
Dimensions
width vs innerWidth vs outerWidth.
9.1 — The box variants
JavaScript
$("#b").width();        // content box
$("#b").innerWidth();   // + padding
$("#b").outerWidth(true); // + padding + border + margin
$("#b").offset();       // position vs document
.width() of a hidden element (display:none) is 0 — there's no layout to measure. Measure after it's visible, or temporarily make it visible off-screen.
Ch. 10
Creating Elements
Build and insert in one expression.
10.1 — Construct, configure, place
JavaScript
const $li = $("<li>", { "class": "item", text: name });
$("#list").append($li);        // inside, end
$target.prepend(x); $target.before(x); $target.after(x);
The $("<li>", { props }) form sets text safely (escaped) — building elements this way avoids the string-concatenation XSS you'd get from $("<li>" + name + "</li>").
Ch. 11
Removing Elements
remove vs detach vs empty.
11.1 — Three different removals
JavaScript
$(".x").remove();   // gone + its data/handlers freed
$(".x").detach();   // removed but KEEPS data/handlers (re-insert later)
$(".x").empty();    // keep .x, drop its children
Use detach() only if you'll re-insert and need the handlers; otherwise remove() so jQuery cleans up bound data/events. Manually $el[0].remove() via native API skips that cleanup → potential leak.
Ch. 12
.data()
A subtle store that isn't quite the data-* attribute.
12.1 — Initial read vs the cache
JavaScript
// <div data-id="42">
$("#d").data("id");          // 42  (number — type-coerced!)
$("#d").data("id", 99);      // updates the jQuery cache only
$("#d").attr("data-id");     // still "42" — attribute unchanged
.data() reads data-* once into an internal cache and type-coerces ("42"42, "true"true). Writing with .data() does NOT update the DOM attribute. Mixing .data() and .attr("data-…") on the same key is a reliable source of confusion.
Ch. 13
Binding Events
on/off — and never inline.
13.1 — on() is the only one you need
JavaScript
$("#btn").on("click", handler);
$("#btn").off("click", handler);
$("#f").on("submit", (e) => { e.preventDefault(); });
The old .click(), .bind(), .live(), .delegate() helpers are all just .on() underneath and several are removed in jQuery 3+. Standardise on .on()/.off().
Ch. 14
Delegation
One handler for current and future elements.
14.1 — Bind to a stable parent
JavaScript
$("#list").on("click", "li.item", function (e) {
  // `this` is the matched li, even if added later
  $(this).toggleClass("selected");
});
Binding directly to $(".item") attaches N handlers and misses elements added afterwards. Delegated binding (parent.on(evt, selector, fn)) is one handler that covers dynamic content — the correct default for lists.
Ch. 15
The Event Object
Normalised across browsers.
15.1 — Stop, prevent, data
JavaScript
$("a").on("click", (e) => {
  e.preventDefault();      // stop the default action
  e.stopPropagation();    // stop bubbling
  e.target; e.currentTarget; e.which;
});
return false; in a jQuery handler does BOTH preventDefault and stopPropagation — often more than intended (it can break delegation/analytics). Call the specific one you mean.
Ch. 16
Effects
fade/slide/animate — and the queue.
16.1 — Built-ins and custom animate
JavaScript
$(".x").fadeIn(200).delay(500).fadeOut(200);
$(".x").animate({ left: "+=100" }, 300, fn);
$(".x").stop(true, true);   // clear queue, jump to end
Effects queue per element — rapid hover can stack dozens of animations ("animation jank" on menus). Call .stop(true, true) before starting a new one, or prefer CSS transitions for hover/UI state.
Ch. 17
Ajax
$.ajax and the shorthands.
17.1 — Request, handle, fail
JavaScript
$.ajax({ url: "/api", method: "POST", data: payload })
  .done(res => render(res))
  .fail((xhr) => show(xhr.status))
  .always(() => spinner.hide());
In greenfield code prefer the native fetch — but in a jQuery codebase, keep one consistent style. $.get/$.post/$.getJSON are thin wrappers over $.ajax; contentType/processData trip people up on file uploads.
Ch. 18
Deferred & Promises
jqXHR is thenable — but not spec-perfect.
18.1 — Interop with native promises
JavaScript
const p = Promise.resolve($.get("/a"));  // adopt into a real Promise
const data = await $.getJSON("/b");       // awaitable (3.x)
$.when($.get("/a"), $.get("/b")).done(...);   // like Promise.all
jQuery Deferred predates A+ promises and has subtle differences (multiple resolve args, no automatic exception → rejection in old versions). When mixing with async/await, wrap in Promise.resolve() to get standard semantics.
Ch. 19
Forms
Serialise and submit.
19.1 — serialize / serializeArray
JavaScript
const qs = $("#f").serialize();        // "a=1&b=2"
const fd = new FormData($("#f")[0]);  // for file uploads
.serialize() skips disabled fields, unchecked boxes, and file inputs. For uploads use native FormData on the raw form node ($form[0]) and let jQuery send it with processData:false, contentType:false.
Ch. 20
Utilities & $.each
The static helpers.
20.1 — Iterate, map, extend
JavaScript
$.each(arr, (i, v) => { /* note: index FIRST */ });
$("li").each(function (i) { /* this = node */ });
$.extend({}, defaults, options);   // shallow merge
$.each(arr, fn) passes (index, value) — the opposite order of native Array.forEach(value, index). Swapping them is an everyday bug; native forEach/map are usually clearer now.
Ch. 21
Plugins
Extending $.fn the correct way.
21.1 — A well-behaved plugin
JavaScript
$.fn.highlight = function (opts) {
  const o = $.extend({ color: "yellow" }, opts);
  return this.each(function () {
    $(this).css("background", o.color);
  });   // return this → keep chainable
};
A plugin must return this.each(...) so it stays chainable and operates on every matched element. Merge options over defaults with $.extend; never mutate the passed options object.
Ch. 22
noConflict
Sharing the page with other libraries.
22.1 — Release the $
JavaScript
var jq = $.noConflict();
jq(function ($) {
  // $ is jQuery again, only inside here
});
Use noConflict() when another library also owns $ (or two jQuery versions coexist during a migration). The IIFE-with-$-parameter pattern keeps plugin code readable while being safe globally.
Ch. 23
Performance
The cheap wins in a jQuery codebase.
23.1 — Cache, batch, delegate
JavaScript
const $list = $("#list");   // cache — don't re-query in a loop
const html = rows.map(r => `<li>${r}</li>`).join("");
$list.html(html);   // one DOM write, not N appends
Re-running $(selector) inside a loop and append-ing per iteration are the two classic jQuery slowdowns. Cache the set once; build markup as a string and write it in a single operation; delegate events.
Ch. 24
jQuery → Native Map
Every common call has a one-line modern equivalent.
24.1 — The translation table
jQueryNative
$(sel)document.querySelectorAll(sel)
$el.addClass(c)el.classList.add(c)
$el.attr(a, v)el.setAttribute(a, v)
$el.on(e, fn)el.addEventListener(e, fn)
$el.closest(s)el.closest(s)
$el.css(p, v)el.style.setProperty(p, v)
$.ajaxfetch()
$(fn)defer script / DOMContentLoaded
Ch. 25
Migration Strategy
Off jQuery without a rewrite.
25.1 — Incremental removal
  1. Stop adding new jQuery; write new code with native APIs
  2. Replace $.ajax with fetch first — biggest bundle win, lowest risk
  3. Swap leaf utilities (addClass, on, closest) file by file
  4. Remove jQuery plugins or replace with framework components
  5. Drop the jQuery <script> only when grep finds zero $(
The win isn't ideology — it's ~30 KB gzipped and one fewer dependency to keep patched. Migrate where it's cheap (Ajax, class/attr toggling); don't rewrite a working complex plugin just to remove a symbol.
Ch. 26
Common Pitfalls
The recurring jQuery bugs.
26.1 — The list
  • attr("checked") instead of prop("checked")
  • Binding events before DOM-ready / to not-yet-existing elements
  • Direct binding instead of delegation for dynamic lists
  • $.each argument order (index first)
  • .html(userInput) → XSS
  • .data() write not reflecting in the DOM attribute
  • Re-querying selectors in loops; per-item append
  • return false doing more than intended
REF
jQuery Cheatsheet
The 12 calls that are 90% of real jQuery code.
The essentials
JavaScript
$(fn)                          // DOM ready
$("sel")                       // select
.find() / .closest() / .filter()
.text() / .html() / .val()
.addClass() / .toggleClass()
.attr() / .prop()
.on(evt, sel, fn)            // delegated
.append() / .remove()
$.ajax(...).done().fail()
$("<li>", { text })            // safe create