Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

jQuery 4 (legacy code and migration to DOM APIs)  Module 1 – Start here: jQuery in 2026

Loading jQuery: CDN, SRI, npm, slim and noConflict

Load jQuery 4 from a CDN with Subresource Integrity, from npm or your own server, pick the full or slim build, order the scripts and use noConflict.

  • Beginner
  • 15 minutes
  • Examples run with Node.js 24.21.0, jQuery 4.0.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free lesson, Pro answers: 15 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Load jQuery 4 from a CDN with Subresource Integrity, from npm or from your own server
  • Compute an integrity value for a file and explain why one changed byte stops the script
  • Choose between the full and slim builds by checking which parts of jQuery the code calls
  • Order the script tags of jQuery, jQuery Migrate, plugins and page code, and explain defer and async
  • Run jQuery beside another library that uses $, or beside a second copy of jQuery, with noConflict()

Before you start

What you’ll learn · Before you start

What you will learn

  • Load jQuery 4 from a CDN with Subresource Integrity, from npm or from your own server
  • Compute an integrity value for a file and explain why one changed byte stops the script
  • Choose between the full and slim builds by checking which parts of jQuery the code calls
  • Order the script tags of jQuery, jQuery Migrate, plugins and page code, and explain defer and async
  • Run jQuery beside another library that uses $, or beside a second copy of jQuery, with noConflict()

Before you start

On this page

jquery-4.0.0.min.js is 78,748 bytes, and a browser runs whatever arrives under that name unless the page says what to expect. Loading jQuery takes five decisions: where the file comes from, how the browser can check it, which build to load, where its tag goes among the other scripts, and what happens when something else on the page wants the name $.

This lesson makes each decision with the official files: it computes their integrity values and sizes, then runs pages in jsdom, a DOM implementation for Node.js, that load the slim build, put a plugin in the wrong place, share $ with another library and hold two copies of jQuery.

Each place to load jQuery 4 from fits a different site

A page can take jQuery from a CDN: A content delivery network: servers in many places that keep copies of files such as jquery-4.0.0.min.js and send each visitor the copy from a server near them., from the npm package through a build step, or from its own server. Each comes with its own rules:

Where from How the page gets it It fits when
A CDN A script tag that names the file on code.jquery.com, with its integrity value The site has no build step, and its rules allow another site’s files
npm npm install jquery, then import $ from 'jquery' in code that a bundler builds The site already builds its JavaScript
Your own server The same file, copied to your site and loaded from there A strict Content Security Policy, or no third party in the way

code.jquery.com publishes every release with its integrity value (releases.jquery.com), and the copy that jsDelivr serves from the npm package has the same hash, OaVG6p…, by jsDelivr’s own file list. Google Hosted Libraries, by contrast, stops at 3.7.1 (Hosted Libraries).

The npm route behaves differently from a script tag. The package sends a bundler’s import to jQuery’s ES module build (npm registry), and that build ends with jQueryFactory( window, true ) and export default jQuery (jquery-4.0.0.module.js): the true tells jQuery not to create the globals jQuery and $. Your modules get jQuery from the import, and an old plugin that looks for window.jQuery finds nothing.

Two things count against a public CDN today. Its old advantage, that visitors already had the file from other sites, is gone: since version 86, Chrome keys its HTTP cache by the site of the page as well as by the URL (Chrome for Developers), so each site downloads its own copy. And rules may forbid it: a site whose Content Security Policy says script-src 'self' runs scripts from its own origin only (CSP Level 3), and WordPress.org’s plugin rules forbid calling scripts from remote servers and bundling libraries that WordPress already ships (common issues).

Checkpoint

Check: CDN, npm or your own server

  1. Question 1 of 2 Two different sites load the same jquery-4.0.0.min.js from code.jquery.com. A visitor opens the first site, then the second, in Chrome. Does the second site reuse the copy the first one cached?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: No, because Chrome keys its HTTP cache by the site of the page as well as by the URL

    The old argument for a public CDN, that visitors already have the file cached from other sites, no longer holds in a partitioned cache. Choose where to load jQuery from for other reasons: your rules, your CSP and your platform.

  2. Question 2 of 2 Why may a plugin listed on WordPress.org not load jQuery from a CDN?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Its rules forbid loading scripts from remote servers, and plugins must use the libraries WordPress ships

    For a WordPress plugin the answer to "where do I load jQuery from" is fixed: use WordPress's copy by declaring jquery as a dependency of your script.

Subresource Integrity lets the browser check the file

A script tag for a CDN copy carries two more attributes. integrity holds the hash of the file you expect, and crossorigin="anonymous" asks for the file with CORS: Cross-Origin Resource Sharing: the HTTP headers with which a server lets pages from other sites read its responses. A script from another site with an integrity value is fetched with CORS, which crossorigin="anonymous" asks for. and without cookies, which the check needs for a file from another site. That pair is Subresource Integrity (SRI): A browser check: a script or style tag carries the hash of the file it expects in its integrity attribute, and the browser refuses to use a downloaded file whose hash is different.. The specification requires browsers to support SHA-256, SHA-384 and SHA-512, calls SHA-384 “a good baseline”, and says that a browser refuses a file whose hash does not match. Step through one check:

Interactive · message by message How the browser checks jQuery before it runs it Step through one script tag with integrity and crossorigin attributes, from the request to the decision.
How the browser checks jQuery before it runs it, step 6 of 6. Messages so far: The browser to code.jquery.com: GET jquery-4.0.0.min.js; code.jquery.com to The browser: 78,748 bytes + CORS header; The browser to Your page: runs jQuery 4.0.0.Your pagethe script tagThe browsera changed byte: refusedcode.jquery.comserves the fileGET jquery-4.0.0.min.js78,748 bytes + CORS headerruns jQuery 4.0.0no jQuery, an error event

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The tag. The script tag names jquery-4.0.0.min.js and carries its integrity value, sha256-OaVG6p…, with crossorigin="anonymous".
  2. A CORS request. Because of crossorigin, the browser asks for the file with CORS and without cookies. The check needs CORS for a file from another site.
  3. The file arrives. The CDN answers with the 78,748 bytes of the file and a header that lets other sites read it.
  4. The hash. The browser computes the SHA-256 hash of the bytes it received and compares it with the value in the tag. Here they are equal.
  5. The script runs. Only now does the browser run the file. A copy with even one changed byte would have another hash.
  6. When a byte differs. If the hashes differ, the browser refuses the file, as if the request had failed, and fires an error event on the tag. jQuery is then not on the page at all.

The integrity value is nothing secret: it is the hash of the file’s bytes, written in base64 after the algorithm’s name. This program computes it for the official file, compares it with the value releases.jquery.com publishes, then changes one byte, as a tampered or damaged copy would:

The integrity value of jquery-4.0.0.min.js, and one changed byte JavaScript · sri.mjs
// sri.mjs: the integrity value of jquery-4.0.0.min.js, worked out from its bytes, and what one changed byte does to it.
import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';

// The value releases.jquery.com publishes for this file.
const PUBLISHED = 'sha256-OaVG6prZf4v69dPg6PhVattBXkcOWQB62pdZ3ORyrao=';
const integrity = (algorithm, bytes) => `${algorithm}-${createHash(algorithm).update(bytes).digest('base64')}`;

const file = readFileSync(new URL('./vendor/jquery-4.0.0.min.js', import.meta.url));
console.log(`jquery-4.0.0.min.js: ${file.length} bytes`);
for (const algorithm of ['sha256', 'sha384', 'sha512']) console.log(integrity(algorithm, file));
console.log(`sha256 value equals the published one: ${integrity('sha256', file) === PUBLISHED}`);

// One changed byte, as in a tampered or damaged copy: the "j" of "jQuery" in the licence comment becomes "J".
const changed = Buffer.from(file);
const at = changed.indexOf('jQuery');
changed[at] = 'J'.charCodeAt(0);
console.log(`byte ${at} changed: ${integrity('sha256', changed)}`);
console.log(`equals the published one: ${integrity('sha256', changed) === PUBLISHED}`);

Output

jquery-4.0.0.min.js: 78748 bytes
sha256-OaVG6prZf4v69dPg6PhVattBXkcOWQB62pdZ3ORyrao=
sha384-fgGyf7Mo7DURSOMnOy7ed+dkq5Job205Gnzu6QIg0BOHKaqt4D76Dt8VlDCzcMHV
sha512-8LENNbXmzI/Gbj+OwXmqR6V4QaUAw0/porPzy1+dQoJqC0JPHedWoe0DDOTL2uHA5XXJyIsPtiMHH86pVlay6A==
sha256 value equals the published one: true
byte 4 changed: sha256-JscA6SnPEzBScjFQkf/gLUOohHN9C3eSgY5g34MV7s8=
equals the published one: false

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node sri.mjs

The SHA-256 value equals the published one, so a tag with it runs this exact file and nothing else. Changing a single byte, byte 4, the j of jQuery in the licence comment, gave a completely different hash, so a browser would refuse that copy. The SHA-384 and SHA-512 values check the same file with longer hashes; any one of them works in the tag.

Hash Generator Drop a downloaded jquery-4.0.0.min.js on the hash generator, read its SHA-384 SRI value, and compare it with the one in your tag.

A new version with the old integrity value

Wrong: changing jquery-3.7.1.min.js to jquery-4.0.0.min.js in a tag and keeping its integrity value, or leaving out crossorigin: the browser blocks the script either way, and the page loses jQuery. Right: change the address and the value together, copied from releases.jquery.com or computed from the file, and keep crossorigin="anonymous".

Checkpoint

Check: Subresource Integrity

  1. Question 1 of 2 The file a CDN sends does not match the integrity value of its script tag. What does the browser do?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: It refuses to run the file and fires an error event on the script tag

    A failed integrity check is a network error for the script: it does not run, and the error event lets your code notice it. One changed byte is enough, as the run of sri.mjs showed.

  2. Question 2 of 2 A script tag loads jQuery from another site with an integrity value but without a crossorigin attribute. What goes wrong?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The browser cannot check a file from another site without CORS, so it blocks the script

    Copy both attributes from the CDN's script tag. integrity without crossorigin is a tag that the browser refuses to run.

Full or slim: check what the code calls first

jQuery comes in two builds. The slim build: A smaller build of jQuery without its Ajax and effects modules; in jQuery 4 it also leaves out Deferreds, Callbacks and queues. Its version string is 4.0.0+slim. leaves out modules that many pages never use; the 4.0 upgrade guide lists what it lacks from 4.0 onwards (upgrade guide). First, what it saves, measured on the official files:

The size of each build, raw and compressed JavaScript · sizes.mjs
// sizes.mjs: each build's size in bytes on disk, and compressed with gzip (level 9) and Brotli (quality 11), the two
// ways a server or a CDN usually sends it.
import { readFileSync } from 'node:fs';
import { brotliCompressSync, constants, gzipSync } from 'node:zlib';

const files = ['jquery-4.0.0.min.js', 'jquery-4.0.0.slim.min.js', 'jquery-3.7.1.min.js'];
const bytes = (n) => n.toLocaleString('en-US').padStart(8);

console.log(`${'file'.padEnd(25)} ${'on disk'.padStart(8)} ${'gzip'.padStart(8)} ${'Brotli'.padStart(8)}`);
for (const name of files) {
  const file = readFileSync(new URL(`./vendor/${name}`, import.meta.url));
  const gzip = gzipSync(file, { level: 9 }).length;
  const brotli = brotliCompressSync(file, { params: { [constants.BROTLI_PARAM_QUALITY]: 11 } }).length;
  console.log(`${name.padEnd(25)} ${bytes(file.length)} ${bytes(gzip)} ${bytes(brotli)}`);
}

Output

file                       on disk     gzip   Brotli
jquery-4.0.0.min.js         78,748   27,530   24,992
jquery-4.0.0.slim.min.js    56,032   19,461   17,691
jquery-3.7.1.min.js         87,533   30,342   27,445

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node sizes.mjs

Compressed the way a server sends it, the slim build saves 8,069 bytes with gzip and 7,301 with Brotli, about 8 KB once per visitor, since the browser caches the file; the release post puts the slim build at around 19.5k bytes gzipped, and the measurement gives 19,461. 4.0.0 is also 2,812 bytes smaller than 3.7.1 gzipped. Second, what the saving costs. This script looks up twelve parts of jQuery, then calls one that the slim build lacks; it ran on the slim build, on the slim build of 3.7.1 in its place, and on the full build:

One script on three builds

builds/slim.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>The slim build of jQuery 4.0.0</title>
  </head>
  <body>
    <p id="cart">Cart</p>
    <script src="https://code.jquery.com/jquery-4.0.0.slim.min.js" integrity="sha256-8DGpv13HIm+5iDNWw1XqxgFB4mj+yOKFNb+tHBZOowc=" crossorigin="anonymous"></script>
    <script src="parts.js"></script>
  </body>
</html>

builds/full.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>The full build of jQuery 4.0.0</title>
  </head>
  <body>
    <p id="cart">Cart</p>
    <script src="https://code.jquery.com/jquery-4.0.0.min.js" integrity="sha256-OaVG6prZf4v69dPg6PhVattBXkcOWQB62pdZ3ORyrao=" crossorigin="anonymous"></script>
    <script src="parts.js"></script>
  </body>
</html>

builds/parts.js

// parts.js: which parts of jQuery does the loaded build have? Each name is only looked up, so nothing is sent anywhere.
console.log(`jQuery.fn.jquery: ${jQuery.fn.jquery}`);
const parts = {
  '$.ajax': $.ajax,
  '$.getJSON': $.getJSON,
  '.load()': $.fn.load,
  '.animate()': $.fn.animate,
  '.fadeIn()': $.fn.fadeIn,
  '$.Deferred': $.Deferred,
  '$.when': $.when,
  '$.Callbacks': $.Callbacks,
  '.delay()': $.fn.delay,
  '.queue()': $.fn.queue,
  '.show()': $.fn.show,
  '.serialize()': $.fn.serialize,
};
for (const [name, value] of Object.entries(parts)) {
  console.log(`${name.padEnd(12)} ${typeof value === 'function' ? 'present' : 'missing'}`);
}

// What code that calls a missing part sees:
$('#cart').fadeIn();
console.log('fadeIn() ran');
What parts.js logged on each build JavaScript · builds.mjs
// Prints what builds/parts.js logged in jsdom on the slim build (then on 3.7.1's slim build) and on the full build.
import { show } from './show.mjs';

for (const page of ['builds/slim.html', 'builds/full.html']) {
  console.log(`${page}:`);
  show(page);
}

Output

builds/slim.html:
jQuery.fn.jquery: 4.0.0+slim
$.ajax       missing
$.getJSON    missing
.load()      missing
.animate()   missing
.fadeIn()    missing
$.Deferred   missing
$.when       missing
$.Callbacks  missing
.delay()     missing
.queue()     missing
.show()      present
.serialize() present
Uncaught TypeError: $(...).fadeIn is not a function
(jQuery 3.7.1 logged other lines:)
  line 1: jQuery.fn.jquery: 3.7.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween
  line 7: $.Deferred   present
  line 8: $.when       present
  line 9: $.Callbacks  present
  line 10: .delay()     present
  line 11: .queue()     present
builds/full.html:
jQuery.fn.jquery: 4.0.0
$.ajax       present
$.getJSON    present
.load()      present
.animate()   present
.fadeIn()    present
$.Deferred   present
$.when       present
$.Callbacks  present
.delay()     present
.queue()     present
.show()      present
.serialize() present
fadeIn() ran

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node builds.mjs

The slim build of 4.0.0 has no Ajax ($.ajax, $.getJSON, .load()), no effects (.animate(), .fadeIn()), no Deferreds or Callbacks and no queues, and calling .fadeIn() threw TypeError: $(...).fadeIn is not a function. .show() and .serialize() stayed. The slim build of 3.7.1 still had $.Deferred, $.when, $.Callbacks, .delay() and .queue(), so a page that ran fine on 3.x’s slim build can break on 4.0’s. The version string names the build: 4.0.0+slim, against 4.0.0 for the full build.

Search before you switch

Before you load the slim build, search the page’s code and every plugin it loads for .ajax(, .getJSON(, .post(, .load(, .animate(, .fade, .slide, Deferred, .when(, Callbacks, .delay( and .queue(, and for $.get( and jQuery.get(. One match in code you do not control is a reason to keep the full build.

Checkpoint

Check: The full and slim builds

  1. Question 1 of 2 A page moves to the slim build of jQuery 4.0.0. Which line of its code now fails?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: $('#note').fadeIn();

    jQuery 4's slim build leaves out Ajax, effects, Deferreds, Callbacks and queues. Calls into any of them throw a TypeError; everything else works as in the full build.

  2. Question 2 of 2 Gzipped, the full build of jQuery 4.0.0 is 27,530 bytes and the slim build 19,461. How many bytes does the slim build save?

    Type a number.

    A hint

    Subtract the slim build's size from the full build's.

    Show the answer: Question 2 of 2

    Answer: 8069 bytes

    27,530 − 19,461 = 8,069 bytes, about 8 KB, once per visitor, since the browser caches the file. With Brotli the saving is 24,992 − 17,691 = 7,301 bytes.

Scripts run in order: jQuery, Migrate, plugins, your code

A classic script, one without defer or async, runs as soon as the browser reaches its tag, before the rest of the HTML is read (HTML Standard). So the tags of a page run in their order, and each script sees only what the scripts before it created. jQuery comes first, jQuery Migrate: A plugin from the jQuery team, loaded right after jQuery, that brings back APIs a newer jQuery removed or changed and, in its development build, logs a warning each time code uses one, so the code can be fixed before the upgrade. More in “jQuery in 2026: versions, support and where it runs” straight after it, then the plugins, then the page’s own code:

Interactive · stage by stage The order the scripts of a page run in Step through the classic scripts of a page, then see what one script in the wrong place does.
The order the scripts of a page run in, step 7 of 7. Showing The HTML is read, jquery-4.0.0.min.js, jquery-migrate-4.0.2.js, cart-badge.js (adds .cartBadge() to jQuery), page.js (calls .cartBadge()). In focus: jquery-4.0.0.min.js, jquery-migrate-4.0.2.js, cart-badge.js, page.js.first: jQuery is not definedThe HTML is readone script tag at a timejquery-4.0.0.min.jsdefines jQuery and $jquery-migrate-4.0.2.jsneeds jQuery to existcart-badge.jsadds .cartBadge() to jQuerypage.jscalls .cartBadge()

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. In document order. A classic script without defer or async runs as soon as the browser reaches it, before the HTML after it is read, so the scripts run in the order of the page.
  2. jQuery first. jQuery runs first and creates the names jQuery and $ on the window.
  3. Then Migrate. jQuery Migrate, if the page uses it, comes straight after jQuery: it patches the jQuery that exists when it runs.
  4. Then the plugins. A plugin adds its methods to jQuery.fn, the prototype of the jQuery that exists when it runs.
  5. Your code last. The page's own code runs last, when jQuery, Migrate and the plugin are all there: it logged badge: 2.
  6. One script too early. With the plugin above jQuery, it ran first and threw ReferenceError: jQuery is not defined, and the later call failed with $(...).cartBadge is not a function.
  7. defer and async. defer keeps this order and runs the scripts once the HTML is read; async runs each one as soon as it arrives, in any order, which breaks plugins.

The same three scripts in the wrong order and in the right one:

Two pages, one change of order

order/wrong.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Scripts in the wrong order</title>
  </head>
  <body>
    <p id="cart">Cart</p>
    <!-- The plugin comes before jQuery. -->
    <script src="cart-badge.js"></script>
    <script src="https://code.jquery.com/jquery-4.0.0.min.js" integrity="sha256-OaVG6prZf4v69dPg6PhVattBXkcOWQB62pdZ3ORyrao=" crossorigin="anonymous"></script>
    <script src="page.js"></script>
  </body>
</html>

order/right.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Scripts in the right order</title>
  </head>
  <body>
    <p id="cart">Cart</p>
    <!-- jQuery first, then the plugin, then the code of the page. -->
    <script src="https://code.jquery.com/jquery-4.0.0.min.js" integrity="sha256-OaVG6prZf4v69dPg6PhVattBXkcOWQB62pdZ3ORyrao=" crossorigin="anonymous"></script>
    <script src="cart-badge.js"></script>
    <script src="page.js"></script>
  </body>
</html>

order/cart-badge.js

// cart-badge.js: a small plugin. It adds .cartBadge() to the copy of jQuery that is loaded when it runs.
(function ($) {
  $.fn.cartBadge = function (count) {
    return this.each(function () {
      $(this).attr('data-count', String(count));
    });
  };
})(jQuery);

order/page.js

// page.js: the page's own code, which needs jQuery and the plugin.
$('#cart').cartBadge(2);
console.log(`badge: ${$('#cart').attr('data-count')}`);
What the two pages logged JavaScript · order.mjs
// Prints what the two pages of order/ logged in jsdom: the plugin before jQuery, then jQuery first.
import { show } from './show.mjs';

for (const page of ['order/wrong.html', 'order/right.html']) {
  console.log(`${page}:`);
  show(page);
}

Output

order/wrong.html:
Uncaught ReferenceError: jQuery is not defined
Uncaught TypeError: $(...).cartBadge is not a function
order/right.html:
badge: 2

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node order.mjs

On the wrong page the plugin ran first and threw ReferenceError: jQuery is not defined; it never attached .cartBadge(), so the page’s code failed too. Read such errors from the first one: the second is only its consequence. On the right page the code logged badge: 2.

defer and async change when a script runs. The HTML Standard runs a defer script once the page has been read, and defer scripts keep the order of their tags; an async script runs as soon as it has arrived, whenever that is. So put defer on jQuery, its plugins and your code alike, or on none of them, and never async: a plugin that arrives before jQuery fails exactly as on the wrong page.

Checkpoint

Check: The order of the scripts

  1. One question A page loads cart-badge.js, a jQuery plugin, in a script tag above jQuery's. Which error appears first?

    Choose one answer.

    Show the answer: One question

    Answer: ReferenceError: jQuery is not defined

    Order matters for classic scripts: jQuery, then Migrate, then plugins, then your code. A plugin above jQuery fails at once, and every call to it fails later.

noConflict() makes room for another library, but two copies remain a bug

When jQuery loads, it remembers what window.jQuery and window.$ held and then takes both names. jQuery.noConflict() gives $ back to its old value, and jQuery.noConflict(true) gives back jQuery as well; both return jQuery, so you can keep it under any name (jQuery.noConflict()). WordPress uses this on every site: the copy of jQuery it builds ends with jQuery.noConflict(); (Gruntfile.js), so on a WordPress page $ is not jQuery. Here a page loads an older library that owns $, then jQuery:

jQuery beside a library that uses $

share/other.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>jQuery beside another library that uses $</title>
  </head>
  <body>
    <p id="cart">Cart</p>
    <!-- other-lib.js defines $ first; jQuery takes the name over until noConflict() gives it back. -->
    <script src="other-lib.js"></script>
    <script src="https://code.jquery.com/jquery-4.0.0.min.js" integrity="sha256-OaVG6prZf4v69dPg6PhVattBXkcOWQB62pdZ3ORyrao=" crossorigin="anonymous"></script>
    <script src="other.js"></script>
  </body>
</html>

share/other-lib.js

// other-lib.js: an older library that also names its main function $: $('cart') finds the element whose id is cart.
window.$ = function (id) {
  return document.getElementById(id);
};

share/other.js

// other.js: give $ back to the other library, then use jQuery by its own name, or as $ inside a function.
jQuery.noConflict();
console.log(`$('cart') is the other library's again: ${$('cart').id === 'cart'}`);

(function ($) {
  // Code that runs at once: the wrapper's parameter $ is jQuery.
  console.log(`inside the wrapper, $ is jQuery ${$.fn.jquery}`);
})(jQuery);

jQuery(function ($) {
  // Code that waits for the page: jQuery passes itself to the function as $.
  $('#cart').text('Cart: 3 items');
  console.log(`ready: ${$('#cart').text()}`);
});
What other.js logged JavaScript · other.mjs
// Prints what share/other.js logged in jsdom, beside a library that also uses $.
import { show } from './show.mjs';

show('share/other.html');

Output

$('cart') is the other library's again: true
inside the wrapper, $ is jQuery 4.0.0
ready: Cart: 3 items

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node other.mjs

After noConflict(), $('cart') was the other library’s lookup by id again. jQuery kept working through two wrappers that give code a $ of its own, as the jQuery Learning Center recommends: (function ($) { … })(jQuery); runs at once, and jQuery(function ($) { … }) waits for the page, because jQuery passes itself to a ready function. Neither touches the global $.

A second copy of jQuery is a different problem. When a theme loads jQuery 3.7.1 and a plugin for it, and another plugin brings jQuery 4.0.0, the second copy takes both names. Step through what each script does to them:

Interactive · step by step Two copies of jQuery on one page Step through the four scripts and watch what window.jQuery and window.$ point to after each one.
Two copies of jQuery on one page, step 6 of 6. Showing jquery-3.7.1.min.js, cart-badge.js, jquery-4.0.0.min.js, two.js, window.jQuery (the 3.7.1 copy again), window.$ (the 3.7.1 copy again), The 3.7.1 copy (has .cartBadge()), The 4.0.0 copy (kept as jq4). In focus: The 3.7.1 copy, The 4.0.0 copy.jquery-3.7.1.min.jscart-badge.jsjquery-4.0.0.min.jstwo.jswindow.jQuerythe 3.7.1 copy againwindow.$the 3.7.1 copy againThe 3.7.1 copyhas .cartBadge()The 4.0.0 copykept as jq4

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The first copy. jQuery 3.7.1 loads and sets both names, window.jQuery and window.$, to itself.
  2. A plugin attaches. cart-badge.js adds .cartBadge() to jQuery.fn of the copy that window.jQuery names at that moment: 3.7.1.
  3. The second copy. jQuery 4.0.0 loads, remembers what the two names held, and sets both to itself. The 3.7.1 copy and its plugin are still in memory, but no name reaches them.
  4. The plugin is gone. two.js finds jQuery 4.0.0 under both names, and typeof jQuery.fn.cartBadge is undefined: a call to .cartBadge() now fails.
  5. noConflict(true). jQuery.noConflict(true) hands both names back to the values 4.0.0 remembered, the 3.7.1 copy, and returns 4.0.0, which the script keeps as jq4.
  6. Two separate copies. jQuery('#cart').cartBadge(3) works again. Each copy keeps its own data: .data('items') is 3 through 3.7.1 and undefined through jq4.

A page with two copies of jQuery

share/two-copies.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Two copies of jQuery on one page</title>
  </head>
  <body>
    <p id="cart">Cart</p>
    <!-- A theme loads jQuery 3.7.1 and a plugin for it; another plugin brings its own jQuery 4.0.0. -->
    <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
    <script src="cart-badge.js"></script>
    <script src="https://code.jquery.com/jquery-4.0.0.min.js" integrity="sha256-OaVG6prZf4v69dPg6PhVattBXkcOWQB62pdZ3ORyrao=" crossorigin="anonymous"></script>
    <script src="two.js"></script>
  </body>
</html>

share/two.js

// two.js: two copies of jQuery on one page. window.jQuery and window.$ name the one that was loaded last.
console.log(`jQuery is ${jQuery.fn.jquery}, and $ is ${$.fn.jquery}`);
console.log(`cartBadge on this copy: ${typeof jQuery.fn.cartBadge}`);

// noConflict(true) hands both names back to the copy loaded before, and returns this one.
const jq4 = jQuery.noConflict(true);
console.log(`after noConflict(true): jQuery is ${jQuery.fn.jquery}, jq4 is ${jq4.fn.jquery}`);

jQuery('#cart').cartBadge(3);
console.log(`badge: ${jQuery('#cart').attr('data-count')}`);

// Each copy keeps its own data and event handlers.
jQuery('#cart').data('items', 3);
console.log(`data('items') through 3.7.1: ${jQuery('#cart').data('items')}, through 4.0.0: ${jq4('#cart').data('items')}`);
What two.js logged JavaScript · two-copies.mjs
// Prints what share/two.js logged in jsdom, on a page with two copies of jQuery.
import { show } from './show.mjs';

show('share/two-copies.html');

Output

jQuery is 4.0.0, and $ is 4.0.0
cartBadge on this copy: undefined
after noConflict(true): jQuery is 3.7.1, jq4 is 4.0.0
badge: 3
data('items') through 3.7.1: 3, through 4.0.0: undefined

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node two-copies.mjs

The 4.0.0 copy had no .cartBadge(), because the plugin had attached itself to 3.7.1, and the two copies kept separate data: .data('items') was 3 through one and undefined through the other. noConflict(true) gave both names back to 3.7.1 and made the plugin reachable again, but the page still downloaded and ran jQuery twice. The real fix is one copy: the platform’s on a platform, your own everywhere else.

Key idea

Load exactly one jQuery, before everything that uses it, and give your own code a local $ with a wrapper instead of taking the global name.

Checkpoint

Check: noConflict() and second copies

  1. Question 1 of 2 A page has called jQuery.noConflict(), and $ belongs to another library. Which code can still use $ for jQuery?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: jQuery(function ($) { $('#cart').text('Cart: 3 items'); });

    noConflict() gives $ back; your code keeps working by naming jQuery, or by receiving it as $, in a ready function or in an immediately invoked function.

  2. Question 2 of 2 A theme loads jQuery 3.7.1 and a plugin for it, and another plugin then loads jQuery 4.0.0. Why does a call to the theme's plugin fail?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The second copy took over window.jQuery and $, and the plugin is attached to the first copy

    Every copy of jQuery is a separate object with its own plugins, data and handlers, and the names belong to the copy loaded last. Load one copy, the platform's, and attach every plugin to it.

Practise: code that works when $ is not jQuery

A shop page loads another library that owns $, then jQuery, then calls noConflict(), as WordPress does. Fix the function that shows the number of items in the cart, so that it waits for the page, uses jQuery without the global $ and shows 99+ for a count above 99. The sample tests run in your browser:

Exercise · Easy · JavaScript

Show the cart count on a page where $ is not jQuery

A shop page loads an older library that owns the name $, then jQuery, then calls jQuery.noConflict(), as WordPress does with its own copy. Fix showCount(win, count) in count.mjs so that it works on that page:

  • When the page is ready, it writes count into the element whose id is cart-count, as text, with jQuery's .text(). A count above 99 is shown as 99+, so the badge keeps its size.
  • It must not use the global $, which belongs to the other library, and it must not change it either: no window.$ = … and no global var $.
  • win is the page's window; reach jQuery through win.jQuery.

The sample tests run without a browser. They give showCount a stand-in for the window whose jQuery behaves like jQuery for this task: called with a function, it runs it once the page is ready and passes itself as the first argument; called with '#cart-count', it returns a collection whose .text(value) records the value. Meanwhile the global $ is a stand-in for the other library, which returns null for anything it does not know.

Starter code · count.mjs

/**
 * Writes the number of items in the cart into #cart-count when the page is ready, on a page whose $ belongs to
 * another library (jQuery.noConflict() was called). A count above 99 is shown as 99+.
 * @param {object} win the page's window
 * @param {number} count
 */
export function showCount(win, count) {
  win.jQuery(function () {
    $('#cart-count').text(count);
  });
}
The sample tests · count.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { showCount } from './count.mjs';

// A stand-in for the page: the other library owns the global $, and win.jQuery behaves like jQuery for this task.
// jQuery(fn) runs fn when the page is ready and passes jQuery to it; jQuery('#cart-count') returns a collection whose
// text(value) records the text it would show (jQuery turns a number into text itself). html() is there too, and
// records that it was used.
function shopPage() {
  const writes = [];
  const collection = {
    length: 1,
    text(value) {
      writes.push(['text', String(value)]);
      return collection;
    },
    html(value) {
      writes.push(['html', String(value)]);
      return collection;
    },
  };
  const ready = [];
  function jQuery(arg) {
    if (typeof arg === 'function') {
      ready.push(arg);
      return jQuery;
    }
    return arg === '#cart-count' ? collection : { length: 0, text: () => collection, html: () => collection };
  }
  const otherLibrary = function $(id) {
    return null;
  };
  globalThis.$ = otherLibrary;
  const win = { jQuery, $: otherLibrary };
  const makeReady = () => ready.splice(0).forEach((fn) => fn(jQuery));
  return { win, writes, otherLibrary, makeReady };
}

test('writes the count as text once the page is ready', () => {
  const page = shopPage();
  showCount(page.win, 3);
  assert.deepEqual(page.writes, [], 'nothing is written before the page is ready');
  page.makeReady();
  assert.deepEqual(page.writes, [['text', '3']]);
});

test('writes 0 as well', () => {
  const page = shopPage();
  showCount(page.win, 0);
  page.makeReady();
  assert.deepEqual(page.writes, [['text', '0']]);
});

test('shows 99+ above 99', () => {
  for (const [count, shown] of [[99, '99'], [100, '99+'], [250, '99+']]) {
    const page = shopPage();
    showCount(page.win, count);
    page.makeReady();
    assert.deepEqual(page.writes, [['text', shown]], `a count of ${count}`);
  }
});

test("leaves the other library's $ alone", () => {
  const page = shopPage();
  showCount(page.win, 5);
  page.makeReady();
  assert.equal(globalThis.$, page.otherLibrary, 'the global $ is still the other library');
  assert.equal(page.win.$, page.otherLibrary, 'window.$ is still the other library');
});
A hint

jQuery passes itself to a ready function: win.jQuery(function ($) { … }) gives the code inside a $ of its own, which is jQuery, while the global $ stays with the other library. Then $('#cart-count').text(count).

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Finish with a round of questions: those on the ideas you missed come first, and each choice is explained when you pick it:

Practice that adapts to you

Practise this lesson

10 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 10 A bundled app imports jQuery with import $ from 'jquery' and also loads an old plugin that uses window.jQuery. Why does the plugin fail?

    Choose one answer.

    Show the answer: Question 1 of 10

    Answer: The ES module build of jQuery 4 creates no global jQuery or $, so window.jQuery is undefined

    A module import keeps jQuery out of the global scope. Code that still expects window.jQuery must be given it, or be imported as a module that receives jQuery.

  2. Question 2 of 10 A site's Content Security Policy says script-src 'self'. Where can its jQuery come from?

    Choose one answer.

    Show the answer: Question 2 of 10

    Answer: Its own server, as a file it hosts or inside its own bundle

    With a strict policy, self-hosting is the simple choice: the file comes from your own origin, and no third party is involved in serving your code.

  3. Question 3 of 10 Which hash algorithms must browsers support in an integrity value?

    Choose one answer.

    Show the answer: Question 3 of 10

    Answer: SHA-256, SHA-384 and SHA-512

    An integrity value is the algorithm, a hyphen and the base64 digest, such as sha384-fgGyf7…. With several values, the browser uses the strongest algorithm among them.

  4. Question 4 of 10 You change a script tag from jquery-3.7.1.min.js to jquery-4.0.0.min.js but keep the old integrity value. What happens?

    Choose one answer.

    Show the answer: Question 4 of 10

    Answer: The browser blocks jQuery, because the value no longer matches the new file

    Update the address and the integrity value together, copied from releases.jquery.com or computed from the file, as sri.mjs does.

  5. Question 5 of 10 What does jQuery.fn.jquery say on a page that runs the slim build of jQuery 4.0.0?

    Choose one answer.

    Show the answer: Question 5 of 10

    Answer: 4.0.0+slim

    The version string is the quickest way to tell the builds apart in a console, and report.js in the first lesson of this module reads it.

  6. Question 6 of 10 A page uses $.Deferred and $.when but no Ajax and no effects. Which jQuery file can it load?

    Choose one answer.

    Show the answer: Question 6 of 10

    Answer: The full build of 4.0.0, unless the Deferreds are replaced with native promises

    The slim build lost more in 4.0 than in 3.x. Check what your code calls against the build you load, not against what slim meant in 3.x.

  7. Question 7 of 10 Why should jQuery and its plugins not be loaded with async?

    Choose one answer.

    Show the answer: Question 7 of 10

    Answer: Each async script runs as soon as it arrives, in any order, so a plugin may run before jQuery

    Use plain classic scripts in order, or put defer on all of them, which keeps document order and runs them once the HTML is read. async suits scripts that depend on nothing and that nothing depends on.

  8. Question 8 of 10 The console says "jQuery is not defined" and then "$(...).cartBadge is not a function". What is the first thing to change?

    Choose one answer.

    Show the answer: Question 8 of 10

    Answer: Move the plugin's script tag below jQuery's

    Read errors from the first one: the second is a consequence of the first. Fix the order of the script tags, or declare the dependency where a platform orders the scripts for you.

  9. Question 9 of 10 Code must run at once, not when the page is ready, on a page where $ is not jQuery. Which wrapper fits?

    Choose one answer.

    Show the answer: Question 9 of 10

    Answer: (function ($) { … })(jQuery);

    Two wrappers cover both cases: an immediately invoked function for code that runs now, the ready function for code that waits. Both give the code a local $ and leave the global one alone.

  10. Question 10 of 10 On a page with two copies of jQuery, code stores .data('items', 3) on #cart through the 3.7.1 copy. What does the 4.0.0 copy read for .data('items')?

    Choose one answer.

    Show the answer: Question 10 of 10

    Answer: undefined, because each copy keeps its own data store

    Two copies mean two sets of plugins, data and event handlers on the same elements. That is why a page should load exactly one jQuery.

Interview questions

Warm-up (fresher to 2–5 years): what do the integrity and crossorigin attributes do on a script tag that loads jQuery from a CDN?

integrity holds the hash of the file the page expects, such as sha256-OaVG6prZf4v69dPg6PhVattBXkcOWQB62pdZ3ORyrao= for jquery-4.0.0.min.js. The browser computes the same hash of the file it downloads and refuses to run it if the two differ, so a CDN that serves a changed file cannot run code on your page. crossorigin="anonymous" asks for the file with CORS and without cookies, which the check needs for a file from another site. Browsers must support SHA-256, SHA-384 and SHA-512, and the value must change whenever the file does, so it pins one exact release.

Two harder questions follow: a page that broke on the slim build, and “$ is not a function” on a WordPress theme. Sketch your answer to each before you read its first step:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    To save bytes, a team switched a page to the slim build of jQuery 4. Now it throws "$.ajax is not a function". How do you decide which build the page should use, and how much does the slim build actually save?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to senior

    On a WordPress site, a theme script that starts with $(document).ready(...) throws "$ is not a function". Explain the cause, give two correct fixes, and name one "fix" you would reject.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • jQuery 4 comes from a CDN with an integrity value, from npm through a bundler, whose ES module build creates no globals, or from your own server, which a strict CSP and platform rules may require.
  • An integrity value is the base64 hash of one exact file, and with crossorigin="anonymous" the browser refuses any copy whose hash differs, even by one byte.
  • The slim build of jQuery 4 saves about 8 KB gzipped but has no Ajax, effects, Deferreds, Callbacks or queues, so search the code before you switch.
  • Classic scripts run in the order of their tags: jQuery, then Migrate, then plugins, then your code; defer keeps that order and async does not.
  • noConflict() gives $ back, wrappers give your code a local $, and a second copy of jQuery takes over both names and leaves the first copy’s plugins and data behind.

Terms in this lesson

CDN
A content delivery network: servers in many places that keep copies of files such as jquery-4.0.0.min.js and send each visitor the copy from a server near them.
CORS
Cross-Origin Resource Sharing: the HTTP headers with which a server lets pages from other sites read its responses. A script from another site with an integrity value is fetched with CORS, which crossorigin="anonymous" asks for.
slim build
A smaller build of jQuery without its Ajax and effects modules; in jQuery 4 it also leaves out Deferreds, Callbacks and queues. Its version string is 4.0.0+slim.
Subresource Integrity (SRI)
A browser check: a script or style tag carries the hash of the file it expects in its integrity attribute, and the browser refuses to use a downloaded file whose hash is different.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.