Utility Functions
Besides working with elements, jQuery has a set of utility functions that are called directly on $, such as $.each() and $.extend(). They handled arrays, objects and promises in the days before JavaScript had good built-in tools for these jobs.
Many utilities have now been removed from jQuery 4 because JavaScript itself does the job, and the rest have clear modern equivalents. In this lesson you will learn what the common utilities do so you can read old code, and which built-in feature to use instead.
Looping and transforming arrays
| jQuery | What it does | Modern JavaScript |
|---|---|---|
$.each(arr, (i, v) => ...) | loop; return false stops the loop | for...of, arr.forEach((v, i) => ...) |
$.each(obj, (key, v) => ...) | loop over an object's own properties | for (const [key, v] of Object.entries(obj)) |
$.map(arr, (v, i) => ...) | map; drops null/undefined, flattens returned arrays | arr.flatMap(...) or map + filter |
$.grep(arr, fn) | keep items where fn returns true | arr.filter(fn) |
$.grep(arr, fn, true) | the opposite: keep items where fn is false | arr.filter((v) => !fn(v)) |
$.inArray(v, arr) | index of v, or -1 | arr.indexOf(v), arr.includes(v) |
$.merge(a, b) | append b into a (changes a) | a.push(...b) or [...a, ...b] |
$.makeArray(list) | turn an array-like into an array | Array.from(list), [...list] |
Two details trip people up. The callback order is (index, value) for $.each but (value, index) for $.map and $.grep. And $.map quietly removes null and undefined results and flattens any array you return, which is why it is closer to flatMap() than to map().
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Array utilities</title>
</head>
<body>
<h3>Unit test marks, Class 9-A</h3>
<pre id="out"></pre>
<script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
<script>
const marks = { Aarav: 72, Priya: 91, Kabir: 28, Meera: 85, Rohan: 31 };
const lines = [];
// $.each over an object: (key, value); return false would stop early
$.each(marks, function (name, score) {
lines.push("jQuery each: " + name + " scored " + score);
});
// Modern: Object.entries with for...of
for (const [name, score] of Object.entries(marks)) {
if (name === "Kabir") break; // stop early, like return false
lines.push("for...of: " + name + " scored " + score);
}
const scores = Object.values(marks);
lines.push("");
lines.push("$.grep pass: " + $.grep(scores, (s) => s >= 33).join(", "));
lines.push("filter pass: " + scores.filter((s) => s >= 33).join(", "));
lines.push("$.grep fail: " + $.grep(scores, (s) => s >= 33, true).join(", "));
// $.map drops null and flattens arrays
const grace = $.map(scores, (s) => (s < 33 ? [s, s + 5] : null));
lines.push("$.map grace marks: " + grace.join(", "));
lines.push("flatMap version: " + scores.flatMap((s) => (s < 33 ? [s, s + 5] : [])).join(", "));
lines.push("$.inArray(85): " + $.inArray(85, scores) + " indexOf: " + scores.indexOf(85));
$("#out").text(lines.join("\n"));
</script>
</body>
</html>Merging objects with $.extend()
$.extend(target, a, b) copies the properties of a and b into target and returns it. Passing true first makes a deep merge, which merges nested objects instead of replacing them. Plugins use it to combine default settings with the user's options.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>$.extend</title>
</head>
<body>
<pre id="out"></pre>
<script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
<script>
const defaults = { theme: "light", currency: "INR", alerts: { sms: true, email: true } };
const userOptions = { theme: "dark", alerts: { email: false } };
const shallow = $.extend({}, defaults, userOptions); // alerts replaced as a whole
const deep = $.extend(true, {}, defaults, userOptions); // alerts merged key by key
const spread = { ...defaults, ...userOptions }; // modern shallow merge
const modernDeep = { ...defaults, ...userOptions, alerts: { ...defaults.alerts, ...userOptions.alerts } };
$("#out").text(
"shallow $.extend: " + JSON.stringify(shallow) + "\n" +
"deep $.extend: " + JSON.stringify(deep) + "\n" +
"spread: " + JSON.stringify(spread) + "\n" +
"spread, nested: " + JSON.stringify(modernDeep) + "\n" +
"defaults intact: " + JSON.stringify(defaults)
);
</script>
</body>
</html>The shallow merge loses sms: true, because the whole alerts object from the user replaces the default one. The {} first argument matters: without it, $.extend(defaults, userOptions) would change defaults itself. To make an independent deep copy of an object (not a merge), modern JavaScript has structuredClone(obj).
Checking types
jQuery 4 removed most of its type-checking helpers. If old code uses them, it will now throw "is not a function":
| Removed in jQuery 4 | Use instead |
|---|---|
$.isArray(x) | Array.isArray(x) |
$.isFunction(x) | typeof x === "function" |
$.isNumeric(x) | Number.isFinite(x) for numbers; for text, write a small check of your own |
$.type(x) | typeof, Array.isArray(), instanceof |
$.trim(s) | s.trim() |
$.parseJSON(s) | JSON.parse(s) |
$.now() | Date.now() |
$.unique(arr) | $.uniqueSort(arr) for DOM elements; [...new Set(arr)] for values |
These still exist in jQuery 4: $.isPlainObject(x) (true for {} literals only, false for arrays and dates), $.isEmptyObject(obj) (modern: Object.keys(obj).length === 0), $.contains(parent, child) (modern: parent.contains(child)) and $.noop, an empty function used as a default callback.
$.proxy(fn, context) also still exists but has been deprecated since jQuery 3.3. Use fn.bind(context) or an arrow function.
Deferred objects and promises
Before JavaScript had promises, jQuery had Deferred objects. A Deferred is a promise that you resolve or reject yourself. AJAX requests and .promise() on animations use them inside. $.when() combines several, like Promise.all().
// jQuery Deferred
function waitFor(ms) {
const dfd = $.Deferred();
setTimeout(() => dfd.resolve(ms), ms);
return dfd.promise(); // hide resolve/reject from callers
}
waitFor(500).done((ms) => console.log("waited", ms));
// Modern: Promise.withResolvers() (ES2024), or simply new Promise
function waitForModern(ms) {
const { promise, resolve } = Promise.withResolvers();
setTimeout(() => resolve(ms), ms);
return promise;
}
console.log("waited", await waitForModern(500));Since jQuery 3.0, Deferreds follow the standard promise rules, so await and .then() work on them. The slim build of jQuery 4 no longer includes Deferred at all, a sign of where things are heading.
Common mistakes
- Mixing up callback orders.
$.eachis(index, value);$.map,$.grepand native array methods are(value, index). - Using
return falseinforEachto stop. It does nothing there; usefor...ofwithbreak. - Forgetting the empty target in
$.extend().$.extend(defaults, options)changesdefaultsfor everyone. - Expecting a shallow merge to keep nested defaults. Use
$.extend(true, ...)or merge the nested object yourself. - Calling removed helpers after upgrading.
$.trim,$.isArrayand friends no longer exist in jQuery 4.
Key points
$.each,$.map,$.grepand$.inArraymap tofor...of/forEach,flatMap/map,filterandindexOf/includes.$.extend({}, defaults, options)merges shallowly;truefirst merges deeply; spread syntax is the modern shallow merge.- jQuery 4 removed
$.trim,$.isArray,$.isFunction,$.isNumeric,$.type,$.parseJSON,$.nowand more; use built-in JavaScript. $.proxyis deprecated; usebind()or arrow functions.- Deferreds are jQuery's older promises;
Promise,Promise.withResolvers()andPromise.all()replace them.
Exercise
You inherit this old code: var names = $.map(students, function (s) { return $.trim(s.name) || null; }); if ($.isArray(names) && $.inArray("Isha", names) !== -1) { console.log("Isha found"); }. It breaks on jQuery 4. Rewrite it with modern JavaScript only (no jQuery), keeping the same behaviour, including dropping empty names. Test it with an array of four students where one name is just spaces.
Show solution
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Modernised utilities</title>
</head>
<body>
<pre id="out"></pre>
<script>
const students = [{ name: " Isha " }, { name: "Farhan" }, { name: " " }, { name: "Ananya" }];
const names = students.map((s) => s.name.trim()).filter((name) => name !== "");
const lines = ["names: " + JSON.stringify(names)];
if (Array.isArray(names) && names.includes("Isha")) {
lines.push("Isha found");
}
document.querySelector("#out").textContent = lines.join("\n");
</script>
</body>
</html>trim() replaces $.trim, map plus filter replaces $.map returning null, and includes() replaces the $.inArray(...) !== -1 check.
Quick Quiz
1. What does $.map([1, 2, 3], (n) => (n === 2 ? null : [n, n])) return?
$.map drops null results and flattens returned arrays.
2. Which call merges options into defaults without changing defaults?
The first argument is the target that gets changed, so pass a new empty object.
3. Old code calls $.trim(text). What should it become in jQuery 4?
$.trim was removed in jQuery 4; strings have had a built-in trim() method for years.
4. What replaces $.proxy(fn, obj)?
bind returns a new function with this fixed to obj, exactly what $.proxy did.
Learn jQuery with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
