# jQuery vs Google Closure Library vs ClojureScript Comparison A comprehensive comparison of jQuery 4.0.0 with Google Closure Library and ClojureScript (with Google Closure), providing equivalent code for every example in the jQuery documentation. --- ## Table of Contents 1. [Overview](#overview) 2. [Installation](#installation) 3. [Core Selection](#core-selection) 4. [Utility Functions](#utility-functions) 5. [DOM Manipulation](#dom-manipulation) 6. [Event Handling](#event-handling) 7. [Traversal](#traversal) 8. [Data Storage](#data-storage) 9. [AJAX](#ajax) 10. [Deferred/Promises](#deferredpromises) 11. [Callbacks](#callbacks) 12. [Animation](#animation) 13. [Security Features](#security-features) --- ## Overview | Aspect | jQuery 4.0.0 | Google Closure Library | ClojureScript + Closure | |--------|-------------|----------------------|------------------------| | **Status** | Active (Jan 2026) | Sunset (Aug 2024) | Active | | **Size** | ~30kb gzipped | Modular (tree-shaken) | Compiled & optimized | | **Philosophy** | Imperative, chaining | Object-oriented, modular | Functional, immutable | | **Browser compat** | Last 3 versions | Cross-browser normalized | Via Closure Compiler | | **Build system** | Optional | Closure Compiler | ClojureScript compiler | ### Key Differences **jQuery**: Designed for simplicity and DOM manipulation with a fluent chaining API. Excellent for quick prototyping and small projects. **Google Closure Library**: A comprehensive, modular library built for large-scale applications. Designed to work with the Closure Compiler for aggressive optimizations. Note: The library was sunset in August 2024. **ClojureScript**: A Clojure dialect that compiles to JavaScript. Automatically includes Google Closure Library and uses the Closure Compiler. Offers functional programming paradigms with immutable data structures. --- ## Installation ### jQuery 4.0.0 ```html ``` ```javascript // ES Module import $ from 'jquery'; // CommonJS const $ = require('jquery'); ``` ### Google Closure Library ```html ``` ```javascript // Modern ES modules (if using Closure Compiler) goog.module('myapp.main'); const dom = goog.require('goog.dom'); const events = goog.require('goog.events'); ``` ### ClojureScript ```clojure ;; deps.edn {:deps {org.clojure/clojurescript {:mvn/version "1.11.132"}}} ;; In your namespace (ns myapp.core (:require [goog.dom :as gdom] [goog.events :as gevents]) (:import [goog.events EventType])) ``` --- ## Core Selection ### jQuery: Select by ID ```javascript // jQuery const $header = $('#header'); ``` ### Google Closure ```javascript // Google Closure const header = goog.dom.getElement('header'); // or shorthand const header = goog.dom.$('header'); ``` ### ClojureScript ```clojure ;; ClojureScript with goog.dom (require '[goog.dom :as gdom]) (def header (gdom/getElement "header")) ;; Or using native JS interop (def header (.getElementById js/document "header")) ``` --- ### jQuery: Select by Class ```javascript // jQuery const $buttons = $('.btn'); ``` ### Google Closure ```javascript // Google Closure - single element const button = goog.dom.getElementByClass('btn'); // Multiple elements const buttons = goog.dom.getElementsByClass('btn'); ``` ### ClojureScript ```clojure ;; ClojureScript (def button (gdom/getElementByClass "btn")) (def buttons (gdom/getElementsByClass "btn")) ;; With dommy library (require '[dommy.core :as dommy :refer-macros [sel sel1]]) (def button (sel1 :.btn)) (def buttons (sel :.btn)) ``` --- ### jQuery: Select by Tag ```javascript // jQuery const $paragraphs = $('p'); ``` ### Google Closure ```javascript // Google Closure const paragraphs = goog.dom.getElementsByTagName('p'); // With class filter const items = goog.dom.getElementsByTagNameAndClass('div', 'item'); ``` ### ClojureScript ```clojure ;; ClojureScript (def paragraphs (gdom/getElementsByTagName "p")) ;; With dommy (def paragraphs (sel :p)) ``` --- ### jQuery: Select with Context ```javascript // jQuery const $items = $('.item', '#container'); ``` ### Google Closure ```javascript // Google Closure const container = goog.dom.getElement('container'); const items = goog.dom.getElementsByClass('item', container); // Using query selector const items = goog.dom.query('.item', container); ``` ### ClojureScript ```clojure ;; ClojureScript (def container (gdom/getElement "container")) (def items (gdom/getElementsByClass "item" container)) ;; With dommy (def items (sel container :.item)) ``` --- ### jQuery: Create Elements ```javascript // jQuery const $newDiv = $('
Content
'); ``` ### Google Closure ```javascript // Google Closure const newDiv = goog.dom.createDom('div', {'class': 'new'}, 'Content'); // Shorthand const newDiv = goog.dom.$dom('div', {'class': 'new'}, 'Content'); // Complex nesting const complex = goog.dom.createDom('div', {'class': 'wrapper'}, goog.dom.createDom('h1', null, 'Title'), goog.dom.createDom('p', null, 'Paragraph') ); ``` ### ClojureScript ```clojure ;; ClojureScript with goog.dom (def new-div (gdom/createDom "div" #js {:class "new"} "Content")) ;; With hiccup-style libraries (hipo, hiccups) (require '[hipo.core :as hipo]) (def new-div (hipo/create [:div.new "Content"])) ;; Complex with hiccup (def complex (hipo/create [:div.wrapper [:h1 "Title"] [:p "Paragraph"]])) ``` --- ## Utility Functions ### jQuery: $.extend() - Object Merging ```javascript // jQuery - Shallow merge const defaults = { color: 'blue', size: 'medium' }; const options = { color: 'red' }; const settings = $.extend({}, defaults, options); // Result: { color: 'red', size: 'medium' } // jQuery - Deep merge const obj1 = { a: { b: 1, c: 2 } }; const obj2 = { a: { b: 3 } }; const result = $.extend(true, {}, obj1, obj2); // Result: { a: { b: 3, c: 2 } } ``` ### Google Closure ```javascript // Google Closure - goog.object goog.require('goog.object'); // Shallow merge (modifies target) const settings = {}; goog.object.extend(settings, defaults, options); // Clone then merge const settings = goog.object.clone(defaults); goog.object.extend(settings, options); // Note: No built-in deep merge - use goog.object.unsafeClone for deep copy ``` ### ClojureScript ```clojure ;; ClojureScript - native merge (immutable, shallow) (def defaults {:color "blue" :size "medium"}) (def options {:color "red"}) (def settings (merge defaults options)) ;; => {:color "red" :size "medium"} ;; Deep merge (def obj1 {:a {:b 1 :c 2}}) (def obj2 {:a {:b 3}}) (def result (merge-with merge obj1 obj2)) ;; => {:a {:b 3 :c 2}} ;; Or use a deep-merge library (require '[clojure.walk :refer [postwalk]]) ``` --- ### jQuery: $.each() - Iteration ```javascript // jQuery - Array iteration $.each(['a', 'b', 'c'], function(index, value) { console.log(`${index}: ${value}`); }); // jQuery - Object iteration $.each({ name: 'John', age: 30 }, function(key, value) { console.log(`${key}: ${value}`); }); // jQuery - Return false to break $.each([1, 2, 3, 4, 5], function(i, num) { if (num > 3) return false; console.log(num); }); // Output: 1, 2, 3 ``` ### Google Closure ```javascript // Google Closure - goog.array and goog.object goog.require('goog.array'); goog.require('goog.object'); // Array iteration goog.array.forEach(['a', 'b', 'c'], function(value, index) { console.log(index + ': ' + value); }); // Object iteration goog.object.forEach({ name: 'John', age: 30 }, function(value, key) { console.log(key + ': ' + value); }); // Break with goog.array.some (return true to stop) goog.array.some([1, 2, 3, 4, 5], function(num) { if (num > 3) return true; console.log(num); return false; }); ``` ### ClojureScript ```clojure ;; ClojureScript - Array iteration (doseq [[index value] (map-indexed vector ["a" "b" "c"])] (println (str index ": " value))) ;; Simpler (run! println ["a" "b" "c"]) ;; Object/map iteration (doseq [[k v] {:name "John" :age 30}] (println (str (name k) ": " v))) ;; Early termination with reduced (reduce (fn [_ num] (if (> num 3) (reduced nil) (println num))) nil [1 2 3 4 5]) ;; Output: 1, 2, 3 ``` --- ### jQuery: $.map() - Transform Array ```javascript // jQuery const numbers = [1, 2, 3, 4]; const doubled = $.map(numbers, n => n * 2); // Result: [2, 4, 6, 8] // Return multiple values const expanded = $.map([1, 2], n => [n, n * 2]); // Result: [1, 2, 2, 4] // Return null to remove const filtered = $.map([1, 2, 3, 4], n => n > 2 ? n : null); // Result: [3, 4] ``` ### Google Closure ```javascript // Google Closure goog.require('goog.array'); // Map const doubled = goog.array.map([1, 2, 3, 4], function(n) { return n * 2; }); // Flat map (expand) const expanded = goog.array.flatten( goog.array.map([1, 2], function(n) { return [n, n * 2]; }) ); // Filter then map (no null removal in map) const filtered = goog.array.filter([1, 2, 3, 4], function(n) { return n > 2; }); ``` ### ClojureScript ```clojure ;; ClojureScript - map (def numbers [1 2 3 4]) (def doubled (map #(* % 2) numbers)) ;; => (2 4 6 8) ;; mapcat for expansion (flat map) (def expanded (mapcat (fn [n] [n (* n 2)]) [1 2])) ;; => (1 2 2 4) ;; filter + map or keep (def filtered (filter #(> % 2) [1 2 3 4])) ;; => (3 4) ;; Or use keep (removes nils) (def filtered (keep #(when (> % 2) %) [1 2 3 4])) ``` --- ### jQuery: $.grep() - Filter Array ```javascript // jQuery const numbers = [1, 2, 3, 4, 5, 6]; // Keep elements where callback returns true const evens = $.grep(numbers, n => n % 2 === 0); // Result: [2, 4, 6] // With invert=true, keep elements where callback returns false const odds = $.grep(numbers, n => n % 2 === 0, true); // Result: [1, 3, 5] ``` ### Google Closure ```javascript // Google Closure goog.require('goog.array'); const numbers = [1, 2, 3, 4, 5, 6]; // Filter const evens = goog.array.filter(numbers, function(n) { return n % 2 === 0; }); // Inverted (negate the condition) const odds = goog.array.filter(numbers, function(n) { return n % 2 !== 0; }); ``` ### ClojureScript ```clojure ;; ClojureScript (def numbers [1 2 3 4 5 6]) ;; Filter (def evens (filter even? numbers)) ;; => (2 4 6) ;; Inverted with remove or complement (def odds (remove even? numbers)) ;; => (1 3 5) ;; Or (def odds (filter odd? numbers)) (def odds (filter (complement even?) numbers)) ``` --- ### jQuery: $.merge() - Merge Arrays ```javascript // jQuery const arr1 = [1, 2]; const arr2 = [3, 4]; $.merge(arr1, arr2); // arr1 is now [1, 2, 3, 4] ``` ### Google Closure ```javascript // Google Closure goog.require('goog.array'); const arr1 = [1, 2]; const arr2 = [3, 4]; // Extend modifies first array goog.array.extend(arr1, arr2); // arr1 is now [1, 2, 3, 4] // Or create new array const merged = goog.array.concat(arr1, arr2); ``` ### ClojureScript ```clojure ;; ClojureScript - concat (immutable, creates new sequence) (def arr1 [1 2]) (def arr2 [3 4]) (def merged (concat arr1 arr2)) ;; => (1 2 3 4) ;; As vector (def merged (into arr1 arr2)) ;; => [1 2 3 4] ``` --- ### jQuery: $.inArray() - Find Index ```javascript // jQuery const arr = ['a', 'b', 'c', 'd']; $.inArray('b', arr); // Returns: 1 $.inArray('x', arr); // Returns: -1 $.inArray('b', arr, 2); // Returns: -1 (starts search at index 2) ``` ### Google Closure ```javascript // Google Closure goog.require('goog.array'); const arr = ['a', 'b', 'c', 'd']; goog.array.indexOf(arr, 'b'); // Returns: 1 goog.array.indexOf(arr, 'x'); // Returns: -1 goog.array.indexOf(arr, 'b', 2); // Returns: -1 ``` ### ClojureScript ```clojure ;; ClojureScript (def arr ["a" "b" "c" "d"]) ;; Using .indexOf on vector (.indexOf arr "b") ;; => 1 (.indexOf arr "x") ;; => -1 ;; Or idiomatic ClojureScript (defn index-of [coll item] (first (keep-indexed #(when (= %2 item) %1) coll))) (index-of arr "b") ;; => 1 (index-of arr "x") ;; => nil ``` --- ### jQuery: $.isPlainObject() / $.isEmptyObject() ```javascript // jQuery $.isPlainObject({}); // true $.isPlainObject({ a: 1 }); // true $.isPlainObject([]); // false $.isPlainObject(new Date()); // false $.isEmptyObject({}); // true $.isEmptyObject({ a: 1 }); // false ``` ### Google Closure ```javascript // Google Closure goog.require('goog.object'); goog.isObject({}); // true (but also true for arrays!) // Check plain object more carefully function isPlainObject(obj) { return goog.isObject(obj) && !goog.isArray(obj) && Object.getPrototypeOf(obj) === Object.prototype; } // Empty object check goog.object.isEmpty({}); // true goog.object.isEmpty({ a: 1 }); // false ``` ### ClojureScript ```clojure ;; ClojureScript (map? {}) ;; true (map? {:a 1}) ;; true (map? []) ;; false ;; Empty check (empty? {}) ;; true (empty? {:a 1}) ;; false ;; For JS objects specifically (object? #js {}) ;; true (goog.object/isEmpty #js {}) ;; true ``` --- ### Removed jQuery Functions (Use Native) ```javascript // jQuery 3.x (deprecated) -> Native JavaScript // $.isArray() -> Array.isArray() if (Array.isArray(myVar)) { /* ... */ } // $.isFunction() -> typeof if (typeof callback === 'function') { /* ... */ } // $.trim() -> String.prototype.trim() var trimmed = userInput.trim(); // $.parseJSON() -> JSON.parse() var data = JSON.parse(jsonString); // $.now() -> Date.now() var timestamp = Date.now(); ``` ### Google Closure Equivalents ```javascript // Google Closure goog.isArray(myVar); goog.isFunction(callback); goog.string.trim(userInput); // JSON.parse is native, or use goog.json.parse for older browsers var timestamp = goog.now(); ``` ### ClojureScript Equivalents ```clojure ;; ClojureScript (array? my-var) (fn? callback) (clojure.string/trim user-input) (js/JSON.parse json-string) (.now js/Date) ``` --- ## DOM Manipulation ### jQuery: DOM Ready ```javascript // jQuery - Shorthand (recommended) $(function() { console.log('DOM ready'); }); // jQuery - Explicit ready $(document).ready(function() { console.log('DOM ready'); }); // jQuery - Arrow function $(() => { console.log('DOM ready'); }); ``` ### Google Closure ```javascript // Google Closure goog.require('goog.dom'); goog.require('goog.events'); // Using DOMContentLoaded goog.events.listen(document, 'DOMContentLoaded', function() { console.log('DOM ready'); }); // Or check if already ready if (document.readyState === 'complete' || document.readyState === 'interactive') { console.log('DOM already ready'); } else { goog.events.listen(document, 'DOMContentLoaded', function() { console.log('DOM ready'); }); } ``` ### ClojureScript ```clojure ;; ClojureScript (require '[goog.events :as gevents]) ;; Using goog.events (gevents/listen js/document "DOMContentLoaded" (fn [_] (println "DOM ready"))) ;; Or with native JS (.addEventListener js/document "DOMContentLoaded" (fn [_] (println "DOM ready"))) ;; Check if already loaded (if (or (= "complete" (.-readyState js/document)) (= "interactive" (.-readyState js/document))) (println "Already ready") (gevents/listen js/document "DOMContentLoaded" (fn [_] (println "DOM ready")))) ``` --- ### jQuery: Append/Remove Elements ```javascript // jQuery - Append $('#container').append('

New paragraph

'); // jQuery - Prepend $('#container').prepend('

First paragraph

'); // jQuery - Remove $('#element').remove(); // jQuery - Empty (remove children) $('#container').empty(); ``` ### Google Closure ```javascript // Google Closure goog.require('goog.dom'); const container = goog.dom.getElement('container'); // Append const newP = goog.dom.createDom('p', null, 'New paragraph'); goog.dom.appendChild(container, newP); // Prepend const firstP = goog.dom.createDom('p', null, 'First paragraph'); goog.dom.insertChildAt(container, firstP, 0); // Remove const element = goog.dom.getElement('element'); goog.dom.removeNode(element); // Empty (remove children) goog.dom.removeChildren(container); ``` ### ClojureScript ```clojure ;; ClojureScript with goog.dom (require '[goog.dom :as gdom]) (def container (gdom/getElement "container")) ;; Append (let [new-p (gdom/createDom "p" nil "New paragraph")] (gdom/appendChild container new-p)) ;; Prepend (let [first-p (gdom/createDom "p" nil "First paragraph")] (gdom/insertChildAt container first-p 0)) ;; Remove (gdom/removeNode (gdom/getElement "element")) ;; Empty (gdom/removeChildren container) ;; With dommy library (require '[dommy.core :as dommy :refer-macros [sel1]]) (dommy/append! (sel1 :#container) some-element) (dommy/remove! (sel1 :#element)) ``` --- ### jQuery: Class Manipulation ```javascript // jQuery $('#element').addClass('active'); $('#element').removeClass('inactive'); $('#element').toggleClass('visible'); $('#element').hasClass('active'); // returns boolean ``` ### Google Closure ```javascript // Google Closure goog.require('goog.dom.classlist'); const element = goog.dom.getElement('element'); goog.dom.classlist.add(element, 'active'); goog.dom.classlist.remove(element, 'inactive'); goog.dom.classlist.toggle(element, 'visible'); goog.dom.classlist.contains(element, 'active'); // returns boolean // Multiple classes goog.dom.classlist.addAll(element, ['class1', 'class2']); goog.dom.classlist.removeAll(element, ['class1', 'class2']); // Swap classes goog.dom.classlist.swap(element, 'old-class', 'new-class'); ``` ### ClojureScript ```clojure ;; ClojureScript with goog.dom.classlist (require '[goog.dom.classlist :as classlist]) (def element (gdom/getElement "element")) (classlist/add element "active") (classlist/remove element "inactive") (classlist/toggle element "visible") (classlist/contains element "active") ;; returns boolean ;; With dommy (require '[dommy.core :as dommy :refer-macros [sel1]]) (dommy/add-class! (sel1 :#element) :active) (dommy/remove-class! (sel1 :#element) :inactive) (dommy/toggle-class! (sel1 :#element) :visible) (dommy/has-class? (sel1 :#element) :active) ``` --- ### jQuery: CSS/Style Manipulation ```javascript // jQuery - Get style const color = $('#element').css('color'); // jQuery - Set single style $('#element').css('color', 'red'); // jQuery - Set multiple styles $('#element').css({ color: 'red', fontSize: '16px', backgroundColor: '#fff' }); ``` ### Google Closure ```javascript // Google Closure goog.require('goog.style'); const element = goog.dom.getElement('element'); // Get style const color = goog.style.getStyle(element, 'color'); // Set single style goog.style.setStyle(element, 'color', 'red'); // Set multiple styles goog.style.setStyle(element, { 'color': 'red', 'font-size': '16px', 'background-color': '#fff' }); // Other style utilities goog.style.setOpacity(element, 0.5); goog.style.setSize(element, 100, 200); // width, height goog.style.setPosition(element, 10, 20); // x, y goog.style.showElement(element, true); // show/hide ``` ### ClojureScript ```clojure ;; ClojureScript with goog.style (require '[goog.style :as gstyle]) (def element (gdom/getElement "element")) ;; Get style (def color (gstyle/getStyle element "color")) ;; Set single style (gstyle/setStyle element "color" "red") ;; Set multiple styles (gstyle/setStyle element #js {:color "red" :font-size "16px" :background-color "#fff"}) ;; With dommy (dommy/set-style! (sel1 :#element) :color "red") (dommy/set-style! (sel1 :#element) :font-size "16px") ``` --- ### jQuery: Text and HTML Content ```javascript // jQuery - Get/Set text const text = $('#element').text(); $('#element').text('New text content'); // jQuery - Get/Set HTML const html = $('#element').html(); $('#element').html('Bold text'); ``` ### Google Closure ```javascript // Google Closure goog.require('goog.dom'); const element = goog.dom.getElement('element'); // Get/Set text const text = goog.dom.getTextContent(element); goog.dom.setTextContent(element, 'New text content'); // Get/Set HTML (be careful with XSS) const html = element.innerHTML; element.innerHTML = 'Bold text'; // Safe HTML setting (with Trusted Types support) goog.require('goog.dom.safe'); goog.dom.safe.setInnerHtml(element, trustedHtml); ``` ### ClojureScript ```clojure ;; ClojureScript with goog.dom (def element (gdom/getElement "element")) ;; Get/Set text (def text (gdom/getTextContent element)) (gdom/setTextContent element "New text content") ;; Get/Set HTML (def html (.-innerHTML element)) (set! (.-innerHTML element) "Bold text") ;; With dommy (dommy/text (sel1 :#element)) (dommy/set-text! (sel1 :#element) "New text") (dommy/html (sel1 :#element)) (dommy/set-html! (sel1 :#element) "Bold") ``` --- ### jQuery: Attributes ```javascript // jQuery - Get attribute const href = $('a').attr('href'); // jQuery - Set attribute $('a').attr('href', 'https://example.com'); // jQuery - Set multiple attributes $('#img').attr({ src: 'image.jpg', alt: 'Description' }); // jQuery - Remove attribute $('#input').removeAttr('disabled'); ``` ### Google Closure ```javascript // Google Closure const element = goog.dom.getElement('link'); // Get attribute const href = element.getAttribute('href'); // Set attribute element.setAttribute('href', 'https://example.com'); // Set multiple with goog.dom goog.dom.setProperties(element, { 'href': 'https://example.com', 'target': '_blank' }); // Remove attribute element.removeAttribute('disabled'); ``` ### ClojureScript ```clojure ;; ClojureScript (def element (gdom/getElement "link")) ;; Get attribute (def href (.getAttribute element "href")) ;; Set attribute (.setAttribute element "href" "https://example.com") ;; Set multiple (gdom/setProperties element #js {:href "https://example.com" :target "_blank"}) ;; Remove (.removeAttribute element "disabled") ;; With dommy (dommy/attr (sel1 :a) :href) (dommy/set-attr! (sel1 :a) :href "https://example.com") (dommy/remove-attr! (sel1 :#input) :disabled) ``` --- ## Event Handling ### jQuery: Basic Event Binding ```javascript // jQuery - on() method $('#button').on('click', function(e) { console.log('Button clicked'); }); // jQuery - Shorthand $('#button').click(function() { console.log('Button clicked'); }); // jQuery - Multiple events $('#input').on('focus blur', function(e) { console.log('Focus state changed'); }); ``` ### Google Closure ```javascript // Google Closure goog.require('goog.events'); goog.require('goog.events.EventType'); const button = goog.dom.getElement('button'); // Basic listener goog.events.listen(button, goog.events.EventType.CLICK, function(e) { console.log('Button clicked'); }); // Multiple events const input = goog.dom.getElement('input'); goog.events.listen(input, [goog.events.EventType.FOCUS, goog.events.EventType.BLUR], function(e) { console.log('Focus state changed'); } ); ``` ### ClojureScript ```clojure ;; ClojureScript with goog.events (require '[goog.events :as gevents]) (import '[goog.events EventType]) (def button (gdom/getElement "button")) ;; Basic listener (gevents/listen button EventType.CLICK (fn [e] (println "Button clicked"))) ;; Multiple events (doseq [event-type [EventType.FOCUS EventType.BLUR]] (gevents/listen input event-type (fn [e] (println "Focus state changed")))) ;; With dommy (dommy/listen! (sel1 :#button) :click (fn [e] (println "Button clicked"))) ``` --- ### jQuery: Event Delegation ```javascript // jQuery - Event delegation $('#container').on('click', '.item', function(e) { console.log('Item clicked:', $(this).text()); }); ``` ### Google Closure ```javascript // Google Closure - Manual delegation goog.require('goog.events'); goog.require('goog.dom'); const container = goog.dom.getElement('container'); goog.events.listen(container, goog.events.EventType.CLICK, function(e) { // Check if target matches selector const target = e.target; if (goog.dom.classlist.contains(target, 'item')) { console.log('Item clicked:', goog.dom.getTextContent(target)); } // Or find closest matching ancestor const item = goog.dom.getAncestorByClass(target, 'item'); if (item) { console.log('Item clicked:', goog.dom.getTextContent(item)); } }); ``` ### ClojureScript ```clojure ;; ClojureScript - Manual delegation (gevents/listen (gdom/getElement "container") EventType.CLICK (fn [e] (let [target (.-target e)] (when (classlist/contains target "item") (println "Item clicked:" (gdom/getTextContent target)))))) ;; With dommy - built-in delegation (dommy/listen! [(sel1 :#container) :.item] :click (fn [e] (println "Item clicked:" (dommy/text (.-selectedTarget e))))) ``` --- ### jQuery: One-time Events ```javascript // jQuery - one() for single execution $('#button').one('click', function() { console.log('This only fires once'); }); ``` ### Google Closure ```javascript // Google Closure goog.events.listenOnce(button, goog.events.EventType.CLICK, function(e) { console.log('This only fires once'); }); ``` ### ClojureScript ```clojure ;; ClojureScript (gevents/listenOnce button EventType.CLICK (fn [e] (println "This only fires once"))) ;; With dommy (dommy/listen-once! (sel1 :#button) :click (fn [e] (println "This only fires once"))) ``` --- ### jQuery: Removing Event Handlers ```javascript // jQuery - Named handler for removal function clickHandler(e) { console.log('Clicked'); } $('#button').on('click', clickHandler); $('#button').off('click', clickHandler); // Remove all click handlers $('#button').off('click'); // Remove all handlers $('#button').off(); ``` ### Google Closure ```javascript // Google Closure function clickHandler(e) { console.log('Clicked'); } // Listen returns a key for removal const key = goog.events.listen(button, goog.events.EventType.CLICK, clickHandler); // Remove specific handler goog.events.unlistenByKey(key); // Or remove by parameters goog.events.unlisten(button, goog.events.EventType.CLICK, clickHandler); // Remove all handlers from element goog.events.removeAll(button); // Remove all handlers of specific type goog.events.removeAll(button, goog.events.EventType.CLICK); ``` ### ClojureScript ```clojure ;; ClojureScript (defn click-handler [e] (println "Clicked")) ;; Store the key (def key (gevents/listen button EventType.CLICK click-handler)) ;; Remove by key (gevents/unlistenByKey key) ;; Remove by parameters (gevents/unlisten button EventType.CLICK click-handler) ;; Remove all (gevents/removeAll button) ;; With dommy (dommy/listen! (sel1 :#button) :click click-handler) (dommy/unlisten! (sel1 :#button) :click click-handler) ``` --- ### jQuery: Focus Event Order (W3C in 4.0) ```javascript // jQuery 4.0 order (W3C compliant): // blur → focusout → focus → focusin const $input = $('#myInput'); $input.on('blur', () => console.log('1. blur')); $input.on('focusout', () => console.log('2. focusout')); $input.on('focus', () => console.log('3. focus')); $input.on('focusin', () => console.log('4. focusin')); ``` ### Google Closure ```javascript // Google Closure - Uses native event order const input = goog.dom.getElement('myInput'); goog.events.listen(input, goog.events.EventType.BLUR, function() { console.log('blur'); }); goog.events.listen(input, goog.events.EventType.FOCUS, function() { console.log('focus'); }); // focusin/focusout via FocusHandler for cross-browser support goog.require('goog.events.FocusHandler'); const focusHandler = new goog.events.FocusHandler(input); goog.events.listen(focusHandler, goog.events.FocusHandler.EventType.FOCUSIN, function() { console.log('focusin'); }); ``` ### ClojureScript ```clojure ;; ClojureScript (gevents/listen input EventType.BLUR #(println "blur")) (gevents/listen input EventType.FOCUS #(println "focus")) ;; For focusin/focusout (import '[goog.events FocusHandler]) (def focus-handler (FocusHandler. input)) (gevents/listen focus-handler (.-FOCUSIN FocusHandler.EventType) #(println "focusin")) ``` --- ## Traversal ### jQuery: .find() ```javascript // jQuery - Find all paragraphs inside #content $('#content').find('p').addClass('styled'); // Chained traversal $('#nav') .find('li') .find('a') .addClass('nav-link'); ``` ### Google Closure ```javascript // Google Closure const content = goog.dom.getElement('content'); const paragraphs = goog.dom.getElementsByTagName('p', content); goog.array.forEach(paragraphs, function(p) { goog.dom.classlist.add(p, 'styled'); }); // Or with query const links = goog.dom.query('#nav li a'); goog.array.forEach(links, function(a) { goog.dom.classlist.add(a, 'nav-link'); }); ``` ### ClojureScript ```clojure ;; ClojureScript (let [content (gdom/getElement "content") paragraphs (gdom/getElementsByTagName "p" content)] (doseq [p (array-seq paragraphs)] (classlist/add p "styled"))) ;; With dommy (doseq [p (sel (sel1 :#content) :p)] (dommy/add-class! p :styled)) ``` --- ### jQuery: .filter() ```javascript // jQuery - Filter by selector $('li').filter('.active').css('font-weight', 'bold'); // Filter by function $('input').filter(function() { return $(this).val().length > 0; }).addClass('has-value'); ``` ### Google Closure ```javascript // Google Closure goog.require('goog.array'); // Filter by class const items = goog.dom.getElementsByTagName('li'); const activeItems = goog.array.filter(items, function(li) { return goog.dom.classlist.contains(li, 'active'); }); goog.array.forEach(activeItems, function(li) { goog.style.setStyle(li, 'font-weight', 'bold'); }); // Filter by function const inputs = goog.dom.getElementsByTagName('input'); const filledInputs = goog.array.filter(inputs, function(input) { return input.value.length > 0; }); ``` ### ClojureScript ```clojure ;; ClojureScript (->> (array-seq (gdom/getElementsByTagName "li")) (filter #(classlist/contains % "active")) (run! #(gstyle/setStyle % "font-weight" "bold"))) ;; Filter inputs with values (->> (array-seq (gdom/getElementsByTagName "input")) (filter #(> (count (.-value %)) 0)) (run! #(classlist/add % "has-value"))) ;; With dommy (->> (sel :li) (filter #(dommy/has-class? % :active)) (run! #(dommy/set-style! % :font-weight "bold"))) ``` --- ### jQuery: .closest() ```javascript // jQuery - Find parent form $('input').on('blur', function() { const $form = $(this).closest('form'); validateForm($form); }); ``` ### Google Closure ```javascript // Google Closure goog.require('goog.dom'); goog.events.listen(input, 'blur', function(e) { const form = goog.dom.getAncestorByTagNameAndClass(e.target, 'form'); validateForm(form); }); // By class const container = goog.dom.getAncestorByClass(element, 'container'); ``` ### ClojureScript ```clojure ;; ClojureScript (gevents/listen input "blur" (fn [e] (let [form (gdom/getAncestorByTagNameAndClass (.-target e) "form")] (validate-form form)))) ;; By class (def container (gdom/getAncestorByClass element "container")) ``` --- ### jQuery: .has() ```javascript // jQuery - Only select divs that contain a span $('div').has('span').addClass('has-span'); ``` ### Google Closure ```javascript // Google Closure const divs = goog.dom.getElementsByTagName('div'); goog.array.forEach(divs, function(div) { if (goog.dom.getElementsByTagName('span', div).length > 0) { goog.dom.classlist.add(div, 'has-span'); } }); ``` ### ClojureScript ```clojure ;; ClojureScript (->> (array-seq (gdom/getElementsByTagName "div")) (filter #(pos? (.-length (gdom/getElementsByTagName "span" %)))) (run! #(classlist/add % "has-span"))) ``` --- ### jQuery: .index() ```javascript // jQuery $('li.active').index(); // Position among siblings const $items = $('li'); $items.index($('.active')); // Position in collection ``` ### Google Closure ```javascript // Google Closure goog.require('goog.array'); // Among siblings const active = goog.dom.getElementByClass('active'); const siblings = goog.dom.getChildren(active.parentNode); const index = goog.array.indexOf(siblings, active); // In collection const items = goog.dom.getElementsByTagName('li'); const activeItem = goog.dom.getElementByClass('active'); const indexInCollection = goog.array.indexOf(items, activeItem); ``` ### ClojureScript ```clojure ;; ClojureScript (let [active (gdom/getElementByClass "active") siblings (gdom/getChildren (.-parentNode active))] (.indexOf (array-seq siblings) active)) ``` --- ### jQuery: .add() ```javascript // jQuery - Combine selections $('p') .add('span') .add('div.highlight') .addClass('styled'); ``` ### Google Closure ```javascript // Google Closure - Combine into array const combined = goog.array.concat( goog.array.toArray(goog.dom.getElementsByTagName('p')), goog.array.toArray(goog.dom.getElementsByTagName('span')), goog.array.toArray(goog.dom.getElementsByClass('highlight')) ); goog.array.forEach(combined, function(el) { goog.dom.classlist.add(el, 'styled'); }); ``` ### ClojureScript ```clojure ;; ClojureScript (let [combined (concat (array-seq (gdom/getElementsByTagName "p")) (array-seq (gdom/getElementsByTagName "span")) (array-seq (gdom/getElementsByClass "highlight")))] (run! #(classlist/add % "styled") combined)) ``` --- ## Data Storage ### jQuery: .data() ```javascript // jQuery - Set data $('#element').data('userId', 123); $('#element').data('config', { theme: 'dark', lang: 'en' }); // Get data const userId = $('#element').data('userId'); // 123 const config = $('#element').data('config'); // Get all data const allData = $('#element').data(); // HTML5 data attributes are auto-loaded //
$('#user').data('userId'); // 456 $('#user').data('role'); // 'admin' // Remove data $('#element').removeData('temp'); ``` ### Google Closure ```javascript // Google Closure - Using dataset API (HTML5) const element = goog.dom.getElement('element'); // Set data (via dataset) element.dataset.userId = '123'; element.dataset.config = JSON.stringify({ theme: 'dark', lang: 'en' }); // Get data const userId = element.dataset.userId; const config = JSON.parse(element.dataset.config); // Or use a WeakMap for complex data const dataStore = new WeakMap(); function setData(element, key, value) { let data = dataStore.get(element) || {}; data[key] = value; dataStore.set(element, data); } function getData(element, key) { const data = dataStore.get(element) || {}; return key ? data[key] : data; } ``` ### ClojureScript ```clojure ;; ClojureScript - Using dataset (def element (gdom/getElement "element")) ;; Set via dataset (set! (.. element -dataset -userId) "123") ;; Get (.. element -dataset -userId) ;; For complex data, use atoms or state management (def data-store (atom {})) (defn set-data! [element key value] (swap! data-store assoc-in [element key] value)) (defn get-data [element key] (get-in @data-store [element key])) ;; Or use a library like reagent for reactive state ``` --- ## AJAX ### jQuery: $.ajax() and Shortcuts ```javascript // jQuery - Full AJAX $.ajax({ url: '/api/data', method: 'GET', dataType: 'json' }) .done(function(data) { console.log('Success:', data); }) .fail(function(jqXHR, textStatus, error) { console.error('Error:', error); }) .always(function() { console.log('Complete'); }); // jQuery - Shorthand GET $.get('/api/data', function(data) { console.log(data); }); // jQuery - Shorthand POST $.post('/api/data', { name: 'John' }, function(response) { console.log(response); }); // jQuery - Get JSON $.getJSON('/api/data.json', function(data) { console.log(data); }); // jQuery - Get Script $.getScript('https://example.com/script.js') .done(function() { console.log('Script loaded'); }); ``` ### Google Closure ```javascript // Google Closure goog.require('goog.net.XhrIo'); goog.require('goog.events'); // GET request goog.net.XhrIo.send('/api/data', function(e) { const xhr = e.target; if (xhr.isSuccess()) { const data = xhr.getResponseJson(); console.log('Success:', data); } else { console.error('Error:', xhr.getStatus()); } console.log('Complete'); }); // POST request goog.net.XhrIo.send('/api/data', function(e) { const data = e.target.getResponseJson(); console.log(data); }, 'POST', 'name=John', {'Content-Type': 'application/x-www-form-urlencoded'}); // With instance for more control const xhr = new goog.net.XhrIo(); goog.events.listen(xhr, goog.net.EventType.COMPLETE, function(e) { if (xhr.isSuccess()) { console.log(xhr.getResponseJson()); } }); goog.events.listen(xhr, goog.net.EventType.ERROR, function(e) { console.error('Request failed'); }); xhr.send('/api/data', 'GET'); // JSON with headers const headers = {'Content-Type': 'application/json'}; goog.net.XhrIo.send('/api/data', callback, 'POST', JSON.stringify({name: 'John'}), headers); ``` ### ClojureScript ```clojure ;; ClojureScript with goog.net.XhrIo (require '[goog.net.XhrIo :as xhr]) ;; GET request (xhr/send "/api/data" (fn [e] (let [target (.-target e)] (if (.isSuccess target) (println "Success:" (.getResponseJson target)) (println "Error:" (.getStatus target)))))) ;; POST request (xhr/send "/api/data" (fn [e] (println (.getResponseJson (.-target e)))) "POST" (js/JSON.stringify #js {:name "John"}) #js {"Content-Type" "application/json"}) ;; With cljs-ajax library (recommended) (require '[ajax.core :refer [GET POST]]) (GET "/api/data" {:handler (fn [response] (println "Success:" response)) :error-handler (fn [error] (println "Error:" error))}) (POST "/api/data" {:params {:name "John"} :format :json :response-format :json :handler (fn [response] (println response))}) ;; With cljs-http and core.async (require '[cljs-http.client :as http] '[cljs.core.async :refer [ (p/all [(fetch-user) (fetch-posts)]) (p/then (fn [[user posts]] (println "User:" user) (println "Posts:" posts))) (p/catch (fn [error] (println "Failed:" error)))) ``` --- ## Deferred/Promises ### jQuery: $.Deferred() ```javascript // jQuery function asyncOperation() { const deferred = $.Deferred(); setTimeout(function() { const success = Math.random() > 0.5; if (success) { deferred.resolve('Operation succeeded'); } else { deferred.reject('Operation failed'); } }, 1000); return deferred.promise(); } asyncOperation() .done(function(result) { console.log(result); }) .fail(function(error) { console.error(error); }) .always(function() { console.log('Complete'); }); ``` ### Google Closure ```javascript // Google Closure - goog.Promise (or native Promise) goog.require('goog.Promise'); function asyncOperation() { return new goog.Promise(function(resolve, reject) { setTimeout(function() { const success = Math.random() > 0.5; if (success) { resolve('Operation succeeded'); } else { reject('Operation failed'); } }, 1000); }); } asyncOperation() .then(function(result) { console.log(result); }) .thenCatch(function(error) { console.error(error); }) .thenAlways(function() { console.log('Complete'); }); // Or with native Promise function asyncOperation() { return new Promise(function(resolve, reject) { // ... same as above }); } ``` ### ClojureScript ```clojure ;; ClojureScript with core.async channels (require '[cljs.core.async :refer [! chan go timeout]]) (defn async-operation [] (let [c (chan)] (go ( (rand) 0.5) (>! c {:status :success :result "Operation succeeded"}) (>! c {:status :error :error "Operation failed"}))) c)) (go (let [{:keys [status result error]} ( (rand) 0.5) (resolve "Operation succeeded") (reject "Operation failed"))) 1000)))) (-> (async-operation) (p/then #(println %)) (p/catch #(println "Error:" %)) (p/finally #(println "Complete"))) ``` --- ## Callbacks ### jQuery: $.Callbacks() ```javascript // jQuery - Basic usage const callbacks = $.Callbacks(); function fn1(value) { console.log('fn1:', value); } function fn2(value) { console.log('fn2:', value); } callbacks.add(fn1); callbacks.add(fn2); callbacks.fire('hello'); // Output: // fn1: hello // fn2: hello // With flags const onceCallbacks = $.Callbacks('once'); onceCallbacks.add(fn1); onceCallbacks.fire('first'); // fn1: first onceCallbacks.fire('second'); // (nothing - already fired) // Memory flag const memoryCallbacks = $.Callbacks('memory'); memoryCallbacks.fire('done'); memoryCallbacks.add(fn1); // Immediately called with 'done' ``` ### Google Closure ```javascript // Google Closure - Custom implementation goog.provide('app.Callbacks'); app.Callbacks = function(flags) { this.list_ = []; this.fired_ = false; this.lastArgs_ = null; this.once_ = flags && flags.indexOf('once') !== -1; this.memory_ = flags && flags.indexOf('memory') !== -1; }; app.Callbacks.prototype.add = function(fn) { this.list_.push(fn); if (this.memory_ && this.fired_) { fn.apply(null, this.lastArgs_); } }; app.Callbacks.prototype.fire = function() { if (this.once_ && this.fired_) return; this.fired_ = true; this.lastArgs_ = arguments; for (var i = 0; i < this.list_.length; i++) { this.list_[i].apply(null, arguments); } }; // Usage var callbacks = new app.Callbacks(); callbacks.add(fn1); callbacks.fire('hello'); ``` ### ClojureScript ```clojure ;; ClojureScript - Using atoms (defn create-callbacks [& {:keys [once memory]}] (let [callbacks (atom []) fired (atom false) last-args (atom nil)] {:add (fn [f] (swap! callbacks conj f) (when (and memory @fired) (apply f @last-args))) :fire (fn [& args] (when-not (and once @fired) (reset! fired true) (reset! last-args args) (doseq [f @callbacks] (apply f args))))})) ;; Usage (def cbs (create-callbacks)) ((:add cbs) (fn [v] (println "fn1:" v))) ((:add cbs) (fn [v] (println "fn2:" v))) ((:fire cbs) "hello") ;; With memory (def mem-cbs (create-callbacks :memory true)) ((:fire mem-cbs) "done") ((:add mem-cbs) (fn [v] (println "Late callback:" v))) ;; Immediately prints: Late callback: done ``` --- ## Animation ### jQuery: fadeIn/fadeOut ```javascript // jQuery $('.dynamic-content').fadeIn(); $('.dynamic-content').fadeOut(); $('.dynamic-content').fadeToggle(); // With duration $('#element').fadeIn(400); $('#element').fadeOut('slow'); // With callback $('#element').fadeIn(400, function() { console.log('Fade complete'); }); ``` ### Google Closure ```javascript // Google Closure goog.require('goog.fx.dom'); goog.require('goog.events'); const element = goog.dom.getElement('element'); // Fade in const fadeIn = new goog.fx.dom.FadeIn(element, 400); fadeIn.play(); // Fade out const fadeOut = new goog.fx.dom.FadeOut(element, 400); fadeOut.play(); // With callback goog.events.listen(fadeIn, goog.fx.Transition.EventType.END, function() { console.log('Fade complete'); }); // Fade to specific opacity const fade = new goog.fx.dom.Fade(element, 0, 0.5, 400); fade.play(); ``` ### ClojureScript ```clojure ;; ClojureScript with goog.fx.dom (require '[goog.fx.dom :as fx]) (import '[goog.fx Transition]) (def element (gdom/getElement "element")) ;; Fade in (let [anim (fx/FadeIn. element 400)] (.play anim)) ;; Fade out (let [anim (fx/FadeOut. element 400)] (.play anim)) ;; With callback (let [anim (fx/FadeIn. element 400)] (gevents/listen anim (.-END Transition.EventType) (fn [_] (println "Fade complete"))) (.play anim)) ``` --- ### jQuery: slide animations ```javascript // jQuery $('#element').slideDown(); $('#element').slideUp(); $('#element').slideToggle(); // With duration and callback $('#element').slideDown(400, function() { console.log('Slide complete'); }); ``` ### Google Closure ```javascript // Google Closure goog.require('goog.fx.dom'); const element = goog.dom.getElement('element'); const size = goog.style.getSize(element); // Slide down (from 0 height) const slideDown = new goog.fx.dom.Resize( element, [size.width, 0], [size.width, size.height], 400 ); slideDown.play(); // Slide up (to 0 height) const slideUp = new goog.fx.dom.Resize( element, [size.width, size.height], [size.width, 0], 400 ); slideUp.play(); ``` ### ClojureScript ```clojure ;; ClojureScript (require '[goog.fx.dom :as fx] '[goog.style :as gstyle]) (def element (gdom/getElement "element")) (def size (gstyle/getSize element)) ;; Slide down (let [anim (fx/Resize. element #js [(.-width size) 0] #js [(.-width size) (.-height size)] 400)] (.play anim)) ``` --- ### jQuery: Custom Animations ```javascript // jQuery - animate() $('#element').animate({ opacity: 0.5, left: '+=50px', height: 'toggle' }, 1000, 'swing', function() { console.log('Animation complete'); }); ``` ### Google Closure ```javascript // Google Closure - Combining animations goog.require('goog.fx'); goog.require('goog.fx.dom'); goog.require('goog.fx.AnimationParallelQueue'); const element = goog.dom.getElement('element'); // Create parallel animations const queue = new goog.fx.AnimationParallelQueue(); // Fade to 0.5 queue.add(new goog.fx.dom.Fade(element, 1, 0.5, 1000)); // Move right const pos = goog.style.getPosition(element); queue.add(new goog.fx.dom.Slide( element, [pos.x, pos.y], [pos.x + 50, pos.y], 1000 )); goog.events.listen(queue, goog.fx.Transition.EventType.END, function() { console.log('Animation complete'); }); queue.play(); ``` ### ClojureScript ```clojure ;; ClojureScript (require '[goog.fx :as gfx] '[goog.fx.dom :as fx]) (import '[goog.fx AnimationParallelQueue Transition]) (def element (gdom/getElement "element")) (def pos (gstyle/getPosition element)) (let [queue (AnimationParallelQueue.)] (.add queue (fx/Fade. element 1 0.5 1000)) (.add queue (fx/Slide. element #js [(.-x pos) (.-y pos)] #js [(+ 50 (.-x pos)) (.-y pos)] 1000)) (gevents/listen queue (.-END Transition.EventType) (fn [_] (println "Animation complete"))) (.play queue)) ``` --- ## Security Features ### jQuery: Prototype Pollution Prevention ```javascript // jQuery 4.0 blocks these attempts $.extend(true, {}, JSON.parse('{"__proto__": {"polluted": true}}')); $.extend(true, {}, { constructor: { prototype: { polluted: true } } }); // Object.prototype remains unpolluted console.log({}.polluted); // undefined ``` ### Google Closure ```javascript // Google Closure - goog.object doesn't have deep merge by default // Use Object.freeze for protection const safeDefaults = Object.freeze({ setting1: 'value1' }); // Manual safe merge function safeMerge(target, source) { for (const key in source) { if (source.hasOwnProperty(key) && key !== '__proto__' && key !== 'constructor') { target[key] = source[key]; } } return target; } ``` ### ClojureScript ```clojure ;; ClojureScript - Immutable data structures prevent this by design ;; Clojure maps don't have prototype chain issues (def defaults {:setting1 "value1"}) (def user-input (js->clj (js/JSON.parse "{\"__proto__\": {\"polluted\": true}}"))) ;; merge just treats __proto__ as a regular key (def merged (merge defaults user-input)) ;; => {:setting1 "value1", "__proto__" {"polluted" true}} ;; The JS Object.prototype is unaffected ``` --- ### jQuery: XSS Mitigation ```javascript // jQuery - Escape selectors const userInput = 'div">'; const escaped = $.escapeSelector(userInput); $(escaped); // Safe ``` ### Google Closure ```javascript // Google Closure - String escaping goog.require('goog.string'); goog.require('goog.html.SafeHtml'); // HTML escaping const escaped = goog.string.htmlEscape(userInput); // Safe HTML creation const safeHtml = goog.html.SafeHtml.htmlEscape(userInput); ``` ### ClojureScript ```clojure ;; ClojureScript (require '[goog.string :as gstring]) ;; HTML escaping (def escaped (gstring/htmlEscape user-input)) ;; With hiccup libraries, escaping is automatic ;; (hipo/create [:div user-input]) - auto-escaped ``` --- ### jQuery: Trusted Types Support ```javascript // jQuery 4.0 accepts TrustedHTML objects const policy = trustedTypes.createPolicy('myPolicy', { createHTML: (string) => DOMPurify.sanitize(string) }); const trustedContent = policy.createHTML('

Safe content

'); $('#container').html(trustedContent); ``` ### Google Closure ```javascript // Google Closure - Built-in safe DOM methods goog.require('goog.dom.safe'); goog.require('goog.html.SafeHtml'); // Create safe HTML const safeHtml = goog.html.SafeHtml.create('p', {}, 'Safe content'); // Set inner HTML safely goog.dom.safe.setInnerHtml(container, safeHtml); ``` ### ClojureScript ```clojure ;; ClojureScript (require '[goog.dom.safe :as safe] '[goog.html.SafeHtml :as SafeHtml]) (def safe-html (SafeHtml/create "p" nil "Safe content")) (safe/setInnerHtml container safe-html) ``` --- ## Summary: Choosing Between Libraries | Use Case | Recommendation | |----------|---------------| | Quick prototyping | jQuery - simplest API | | Large-scale app | ClojureScript - functional, optimized | | Legacy codebase | jQuery + migrate plugin | | Functional programming | ClojureScript | | Maximum optimization | ClojureScript (Closure Compiler) | | React/modern framework | Consider native APIs or framework tools | ### Migration Path If migrating from jQuery: 1. **To Google Closure**: Replace jQuery calls with `goog.dom`, `goog.events`, `goog.style` 2. **To ClojureScript**: Use `goog.dom` namespace or wrapper libraries like `dommy` ### Key Takeaways - **jQuery** provides the most concise syntax for DOM manipulation - **Google Closure** offers modular, optimizable code (but is sunset) - **ClojureScript** combines functional programming with Closure Library benefits - Modern browsers support many jQuery features natively (`querySelector`, `fetch`, `classList`) --- ## Resources ### jQuery - [jquery.com](https://jquery.com) - [api.jquery.com](https://api.jquery.com) ### Google Closure Library - [google.github.io/closure-library](https://google.github.io/closure-library) - [Closure Cheatsheet](https://anton.shevchuk.name/wp-demo/closure-tutorials/cheatsheet.html) ### ClojureScript - [clojurescript.org](https://clojurescript.org) - [ClojureScript Google Closure Reference](https://clojurescript.org/reference/google-closure-library) - [Dommy Library](https://github.com/plumatic/dommy) - [cljs-ajax](https://github.com/JulianBirch/cljs-ajax) --- *Comparison document for jQuery 4.0.0 (January 2026) vs Google Closure Library vs ClojureScript*