庫(kù)中的 jQuery 去依賴指南:基于 You-Dont-Need-jQuery 夾具的原生 DOM/BOM 替代實(shí)踐)
Atom 倉(cāng)庫(kù)中的 jQuery 去依賴指南:基于 You-Dont-Need-jQuery 夾具的原生 DOM/BOM 替代實(shí)踐【免費(fèi)下載鏈接】atom:atom: The hackable text editor項(xiàng)目地址: https://gitcode.com/gh_mirrors/at/atom本文以 Atom 倉(cāng)庫(kù)中spec/fixtures/git/repo-with-submodules/You-Dont-Need-jQuery/目錄下的印尼語(yǔ)參考文檔 README-id.md 為核心骨架,系統(tǒng)講解如何用瀏覽器原生的 DOM/BOM API 逐項(xiàng)替代 jQuery 的常見(jiàn)用法,覆蓋查詢選擇器、樣式、DOM 操作、Ajax、事件與工具函數(shù)六大類;同時(shí)結(jié)合 Atom 倉(cāng)庫(kù)中引用該夾具的測(cè)試源碼,說(shuō)明這份文檔在倉(cāng)庫(kù)內(nèi)作為 git submodule 測(cè)試數(shù)據(jù)的角色,讀完可掌握一套可直接復(fù)制、并在瀏覽器中落地的 jQuery 遷移方案。一、文檔定位:Atom 倉(cāng)庫(kù)中的一份 jQuery 替代參考該文檔是知名開(kāi)源項(xiàng)目 You-Dont-Need-jQuery(由 OneUI Group 維護(hù),MIT 許可)的印尼語(yǔ)版本 README,其核心觀點(diǎn)是:隨著前端環(huán)境快速演進(jìn),主流瀏覽器已經(jīng)很好地實(shí)現(xiàn)了 DOM/BOM API,不再需要從 jQuery 學(xué)起才能做 DOM 操作與事件處理;在 React、Angular、Vue 等框架普及之后,直接操作 DOM 本身也逐漸成為一種需要謹(jǐn)慎對(duì)待的反模式——jQuery 因此越來(lái)越不必要。該文檔給出了針對(duì) IE 10 的 jQuery 原生替代實(shí)現(xiàn)。在 Atom 倉(cāng)庫(kù)中,這份文檔所在的位置本身就有一個(gè)工程上的看點(diǎn):spec/fixtures/git/repo-with-submodules/是一個(gè)帶 submodule 的 git 倉(cāng)庫(kù)測(cè)試夾具。從源碼結(jié)構(gòu)看:.gitmodules 聲明了兩個(gè) submodule:jstips與You-Dont-Need-jQuery;git.git/config 中同樣記錄了[submodule jstips]和[submodule You-Dont-Need-jQuery]兩組配置;倉(cāng)庫(kù)的提交歷史(git.git 的日志)顯示,最后一次提交信息即為 submodules。Atom 的測(cè)試 spec/project-spec.js 在.observeRepositories()用例中,正是把repo-with-submodules/git.git復(fù)制到臨時(shí)目錄的.git下,驗(yàn)證atom.project能正確觀察到一個(gè)含 submodule 的倉(cāng)庫(kù),并斷言observed[1].getReferenceTarget(refs/heads/master)等于d2b0ad9cbc6f6c4372e8956e5cc5af771b2342e5。也就是說(shuō),本文主角 README 所在的目錄,首先服務(wù)于Atom 能否正確識(shí)別含 submodule 的 Git 倉(cāng)庫(kù)這一測(cè)試目標(biāo);其次,它本身就是一份內(nèi)容完整的 jQuery 原生替代指南,下文即按其目錄結(jié)構(gòu)完整展開(kāi):Query Selector、CSS Style、DOM Manipulation、Ajax、Events、Utilities,外加 Translation 與 Browser Support 兩個(gè)附錄。二、Query Selector:從 jQuery 選擇器到原生查詢 API對(duì)于 class、id、屬性這類常用選擇器,可直接使用document.querySelector或document.querySelectorAll作為替代。兩者區(qū)別:document.querySelector返回第一個(gè)匹配的元素;document.querySelectorAll返回所有匹配元素,組成 NodeList;需要數(shù)組時(shí)可用[].slice.call(document.querySelectorAll(selector) || [])轉(zhuǎn)換;若無(wú)匹配結(jié)果:jQuery 返回[],而 DOM API 返回null——需要警惕空指針問(wèn)題,可以用||提供初始值:document.querySelectorAll(selector) || []。注意(原文檔強(qiáng)調(diào)):querySelector/querySelectorAll相對(duì)偏慢。追求性能時(shí)可改用getElementById、document.getElementsByClassName或document.getElementsByTagName。2.1 基礎(chǔ)查詢(1.0 – 1.3)// 1.0 Query by selector // jQuery $(selector); // Native document.querySelectorAll(selector); // 1.1 Query by class // jQuery $(.class); // Native document.querySelectorAll(.class); // or document.getElementsByClassName(class); // 1.2 Query by id // jQuery $(#id); // Native document.querySelector(#id); // or document.getElementById(id); // 1.3 Query using attribute // jQuery $(a[target_blank]); // Native document.querySelectorAll(a[target_blank]);2.2 后代查找、屬性與 data 屬性(1.4)// 查找子節(jié)點(diǎn) // jQuery $el.find(li); // Native el.querySelectorAll(li); // 查找 body // jQuery $(body); // Native document.body; // 獲取屬性 // jQuery $el.attr(foo); // Native el.getAttribute(foo); // 獲取 data 屬性 // jQuery $el.data(foo); // Native(用 getAttribute) el.getAttribute(data-foo); // 若需要兼容到 IE 11,還可以使用 dataset el.dataset[foo];2.3 Sibling / Previous / Next 兄弟元素(1.5)// 兄弟元素(排除自身) // jQuery $el.siblings(); // Native [].filter.call(el.parentNode.children, function(child) { return child ! el; }); // 前一個(gè)元素 // jQuery $el.prev(); // Native el.previousElementSibling; // 后一個(gè)元素 // jQuery $el.next(); // Native el.nextElementSibling;2.4 closest:向祖先方向查找第一個(gè)匹配元素(1.6)closest返回從當(dāng)前元素出發(fā)、直到 document 為止的第一個(gè)匹配 selector 的元素。原生實(shí)現(xiàn)如下:// jQuery $el.closest(queryString); // Native function closest(el, selector) { const matchesSelector el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector; while (el) { if (matchesSelector.call(el, selector)) { return el; } else { el el.parentElement; } } return null; }這里通過(guò)matches / webkitMatchesSelector / mozMatchesSelector / msMatchesSelector做多引擎前綴兼容,正是面向 IE 10 的寫法。2.5 parentsUntil:獲取限定范圍內(nèi)的祖先鏈(1.7)parentsUntil獲取每個(gè)匹配元素的 ancestor(不包含 selector 搜索得到的當(dāng)前元素本身)。原生實(shí)現(xiàn):// jQuery $el.parentsUntil(selector, filter); // Native function parentsUntil(el, selector, filter) { const result []; const matchesSelector el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector; // 從父元素開(kāi)始匹配 el el.parentElement; while (el !matchesSelector.call(el, selector)) { if (!filter) { result.push(el); } else { if (matchesSelector.call(el, filter)) { result.push(el); } } el el.parentElement; } return result; }2.6 表單取值與索引(1.8)// 輸入框/文本域取值 // jQuery $(#my-input).val(); // Native document.querySelector(#my-input).value; // 獲取 e.currentTarget 在 .radio 集合中的下標(biāo) // jQuery $(e.currentTarget).index(.radio); // Native [].indexOf.call(document.querySelectorAll(.radio), e.currentTarget);2.7 Iframe 內(nèi)容訪問(wèn)(1.9)$(iframe).contents()對(duì)應(yīng)原生的contentDocument:// Iframe 文檔 // jQuery $iframe.contents(); // Native iframe.contentDocument; // 在 Iframe 內(nèi)查詢 // jQuery $iframe.contents().find(.css); // Native iframe.contentDocument.querySelectorAll(.css);三、CSS Style:計(jì)算樣式、類名與尺寸3.1 讀取與設(shè)置樣式(2.1)// 讀取樣式 // jQuery $el.css(color); // Native // 注意:原文檔指出此處存在一個(gè)已知坑——當(dāng) style 屬性值為 auto 時(shí),該方式會(huì)返回 auto const win el.ownerDocument.defaultView; // 第二個(gè)參數(shù)傳 null 表示不獲取偽元素樣式 win.getComputedStyle(el, null).color; // 設(shè)置樣式 // jQuery $el.css({ color: #ff0011 }); // Native el.style.color #ff0011;批量設(shè)置多個(gè)樣式時(shí),原文檔建議參考 oui-dom-utils 包中的setStyles方法實(shí)現(xiàn)(該包為 OneUI 工具庫(kù),倉(cāng)庫(kù)中未包含其源碼,此處僅按文檔表述給出指引)。類名操作則與classList一一對(duì)應(yīng):// 添加類 // jQuery $el.addClass(className); // Native el.classList.add(className); // 移除類 // jQuery $el.removeClass(className); // Native el.classList.remove(className); // 是否包含類 // jQuery $el.hasClass(className); // Native el.classList.contains(className); // 切換類 // jQuery $el.toggleClass(className); // Native el.classList.toggle(className);3.2 Width Height(2.2)理論上 width 與 height 的處理方式相同,以 height 為例:// 窗口高度 $(window).height(); // 不含滾動(dòng)條,行為與 jQuery 一致 window.document.documentElement.clientHeight; // 含滾動(dòng)條 window.innerHeight; // 文檔高度 // jQuery $(document).height(); // Native document.documentElement.scrollHeight; // 元素高度(精確到內(nèi)容區(qū),需扣除邊框與內(nèi)邊距) // jQuery $el.height(); // Native function getHeight(el) { const styles this.getComputedStyles(el); const height el.offsetHeight; const borderTopWidth parseFloat(styles.borderTopWidth); const borderBottomWidth parseFloat(styles.borderBottomWidth); const paddingTop parseFloat(styles.paddingTop); const paddingBottom parseFloat(styles.paddingBottom); return height - borderBottomWidth - borderTopWidth - paddingTop - paddingBottom; } // 精確到整數(shù)(border-box 時(shí)即 height;content-box 時(shí)為 height padding border) el.clientHeight; // 精確到小數(shù)(border-box 時(shí)即 height;content-box 時(shí)為 height padding border) el.getBoundingClientRect().height;3.3 Position Offset(2.3)// Position(相對(duì)最近定位祖先) // jQuery $el.position(); // Native { left: el.offsetLeft, top: el.offsetTop } // Offset(相對(duì)文檔) // jQuery $el.offset(); // Native function getOffset(el) { const box el.getBoundingClientRect(); return { top: box.top window.pageYOffset - document.documentElement.clientTop, left: box.left window.pageXOffset - document.documentElement.clientLeft }; }3.4 Scroll Top(2.4)// jQuery $(window).scrollTop(); // Native (document.documentElement document.documentElement.scrollTop) || document.body.scrollTop;雙寫documentElement/body是為了覆蓋 quirks 模式下滾動(dòng)值記錄在body上的歷史行為。四、DOM Manipulation:增刪節(jié)點(diǎn)與內(nèi)容讀寫4.1 移除節(jié)點(diǎn)(3.1)// jQuery $el.remove(); // Native el.parentNode.removeChild(el);4.2 Text 與 HTML(3.2 – 3.3)// 讀文本 // jQuery $el.text(); // Native el.textContent; // 寫文本 // jQuery $el.text(string); // Native el.textContent string; // 讀 HTML // jQuery $el.html(); // Native el.innerHTML; // 寫 HTML // jQuery $el.html(htmlString); // Native el.innerHTML htmlString;4.3 Append / Prepend(3.4 – 3.5)append在父元素的最后一個(gè)子節(jié)點(diǎn)之后插入新子元素:// jQuery $el.append(div idcontainerhello/div); // Native let newEl document.createElement(div); newEl.setAttribute(id, container); newEl.innerHTML hello; el.appendChild(newEl);prepend則是插入到第一個(gè)子節(jié)點(diǎn)之前:// jQuery $el.prepend(div idcontainerhello/div); // Native let newEl document.createElement(div); newEl.setAttribute(id, container); newEl.innerHTML hello; el.insertBefore(newEl, el.firstChild);4.4 insertBefore / insertAfter(3.6 – 3.7)// 插入到目標(biāo)元素之前 // jQuery $newEl.insertBefore(queryString); // Native const target document.querySelector(queryString); target.parentNode.insertBefore(newEl, target); // 插入到目標(biāo)元素之后 // jQuery $newEl.insertAfter(queryString); // Native const target document.querySelector(queryString); target.parentNode.insertBefore(newEl, target.nextSibling);注意insertAfter沒(méi)有原生同名 API,慣用技巧是把新節(jié)點(diǎn)插入到目標(biāo)兄弟節(jié)點(diǎn)之前。五、Ajax:用 fetch 家族替代 $.ajax文檔對(duì) Ajax 一節(jié)的結(jié)論非常簡(jiǎn)潔:以fetch與fetch-jsonp兩個(gè)庫(kù)替代 jQuery 的 Ajax 能力(原文檔分別指向 fetch-ie8 與 fetch-jsonp 兩個(gè)兼容封裝,前者提供面向 IE8 的兼容)。對(duì)于需要 JSONP 回調(diào)的跨域接口,fetch本身無(wú)法完成,fetch-jsonp正是其補(bǔ)充。六、Events:addEventListener 與 CustomEvent若需要帶命名空間 事件委托的完整事件體系,文檔建議直接參考 OneUI 的oui-dom-events庫(kù)(倉(cāng)庫(kù)內(nèi)不含其源碼,此處按文檔表述給出指引)?;A(chǔ)綁定/解綁/觸發(fā)的替代如下:// 5.1 綁定 // jQuery $el.on(eventName, eventHandler); // Native el.addEventListener(eventName, eventHandler); // 5.2 解綁 // jQuery $el.off(eventName, eventHandler); // Native el.removeEventListener(eventName, eventHandler); // 5.3 觸發(fā)自定義事件(附帶數(shù)據(jù)) // jQuery $(el).trigger(custom-event, {key1: data}); // Native if (window.CustomEvent) { const event new CustomEvent(custom-event, {detail: {key1: data}}); } else { const event document.createEvent(CustomEvent); event.initCustomEvent(custom-event, true, true, {key1: data}); } el.dispatchEvent(event);else分支的createEvent/initCustomEvent是對(duì)不支持new CustomEvent的舊瀏覽器(即 IE 10 檔)的降級(jí)處理,與全文IE 10的兼容目標(biāo)保持一致。七、Utilities:常用工具函數(shù)替代// 6.1 數(shù)組判斷 // jQuery $.isArray(range); // Native Array.isArray(range); // 6.2 去首尾空白 // jQuery $.trim(string); // Native string.trim(); // 6.3 對(duì)象合并(Extend) // Native 側(cè)使用 Object.assign;老瀏覽器可用 ljharb/object.assign polyfill // jQuery $.extend({}, defaultOpts, opts); // Native Object.assign({}, defaultOpts, opts); // 6.4 節(jié)點(diǎn)包含關(guān)系 // jQuery $.contains(el, child); // Native el ! child el.contains(child);contains處的el ! child前置判斷值得留意:按 DOM 規(guī)范,Node.contains對(duì)自身返回true,而 jQuery 的$.contains要求嚴(yán)格祖先關(guān)系,加上這個(gè)不等式判斷才能對(duì)齊語(yǔ)義。八、瀏覽器支持范圍原文檔給出的支持矩陣如下:ChromeFirefoxIEOperaSafariLatest ?Latest ?10 ?Latest ?6.1 ?這意味著上文所有替代寫法都以 IE 10 為兼容性下限;個(gè)別 API(如el.dataset)文檔特別標(biāo)注需 IE 11,使用時(shí)需按目標(biāo)環(huán)境取舍。九、配套測(cè)試:如何用 Karma 驗(yàn)證這些替代寫法該夾具目錄內(nèi)附帶完整的自動(dòng)化測(cè)試,可用來(lái)逐條驗(yàn)證本文的替代寫法與 jQuery 行為等價(jià):test/README.md 給出兩條命令:npm run test單次運(yùn)行全部測(cè)試,npm run tdd以 TDD 模式持續(xù)運(yùn)行;package.json 中對(duì)應(yīng)腳本為karma start --single-run、karma start --auto-watch --no-single-run,另有test-cov(帶 coverage 報(bào)告)與lint(eslint 檢查 src 與 test 目錄);測(cè)試棧為 Karma Mocha Chai,構(gòu)建走 webpack Babel(preset-es2015 與 stage-0),并引入jquery作為對(duì)照依賴,配合 karma.conf.js 使用;測(cè)試文件按文檔章節(jié)劃分:query.spec.js、css.spec.js、dom.spec.js 與 utilities.spec.js;以 query.spec.js 的 1.0 用例為例:在beforeEach中注入一段含嵌套u(yù)l的 HTML,然后斷言$(li.item[data-rolered])與document.querySelectorAll(li.item[data-rolered])數(shù)量一致(均為 2)且逐元素相等;afterEach中又用el.parentNode.removeChild(el)清理——恰好呼應(yīng)了本文 DOM Manipulation 一節(jié)的原生寫法。十、在 Atom 倉(cāng)庫(kù)中定位這份文檔如果你是在 Atom 倉(cāng)庫(kù)里打開(kāi)這份 README,以下路徑構(gòu)成完整的上下文鏈路:文檔主體:spec/fixtures/git/repo-with-submodules/You-Dont-Need-jQuery/README-id.md;其余語(yǔ)言版本(印尼語(yǔ)版文檔的 Translation 章節(jié)列出的譯文,均已轉(zhuǎn)換為倉(cāng)庫(kù)根相對(duì)路徑):README.ko-KR.md、README.zh-CN.md、README-my.md、README.pt-BR.md、README-vi.md、README-ru.md、README-tr.md;submodule 聲明:spec/fixtures/git/repo-with-submodules/.gitmodules 與 git.git/config;消費(fèi)方測(cè)試:spec/project-spec.js 中.observeRepositories()用例,驗(yàn)證含 submodule 的倉(cāng)庫(kù)被atom.project正確發(fā)現(xiàn)、其 master 引用目標(biāo)與夾具中的提交一致。綜合來(lái)看,這份文檔的價(jià)值在于把jQuery 到原生 API的遷移拆成了可直接對(duì)照的代碼對(duì):每一節(jié)都是一個(gè)jQuery 寫法 → Native 寫法的雙欄映射,并針對(duì) IE 10 給出了matchesSelector多前綴、initCustomEvent降級(jí)、dataset版本門檻等細(xì)節(jié);配合目錄內(nèi)自帶的 Karma 測(cè)試,遷移后可以在真實(shí)瀏覽器里逐條驗(yàn)證等價(jià)性?!久赓M(fèi)下載鏈接】atom:atom: The hackable text editor項(xiàng)目地址: https://gitcode.com/gh_mirrors/at/atom創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考