- Cash เป็นไลบรารีทางเลือกแทน jQuery ที่มีขนาดเล็กมาก โดยให้ไวยากรณ์สไตล์ jQuery สำหรับจัดการ DOM บนเบราว์เซอร์สมัยใหม่ IE11+
- ใช้ประโยชน์จากความสามารถของเบราว์เซอร์สมัยใหม่เพื่อลด codebase และทำให้ใช้ เมธอดแบบ chaining ที่คุ้นเคยได้ด้วยขนาดไฟล์ที่เล็กลงมาก
- ไม่ได้ตั้งเป้าให้มี ความเทียบเท่าด้านฟีเจอร์ 100% กับ jQuery แต่ครอบคลุมกรณีใช้งานประจำวันส่วนใหญ่ และ API ที่นำมาใช้งานโดยรวมเข้ากันได้กับ jQuery
- ขนาดเมื่อ minified & gzipped อยู่ที่ 6KB เล็กกว่า jQuery Slim 3.4.1 ที่ 24.4KB อยู่ 76.6% และสามารถลดลงได้อีกด้วย partial builds
- รองรับ codebase แบบ TypeScript, TypeScript types ที่สร้างจากโค้ด, อีเวนต์แบบ namespaced และ partial builds ที่สามารถตัดเมธอดรายตัวออกได้
ปัญหาที่ Cash แก้ไข
- Cash เป็นทางเลือกแทน jQuery สำหรับเบราว์เซอร์สมัยใหม่ โดยให้ selector
$() สไตล์ jQuery และเมธอดคอลเลกชันที่ chain ได้สำหรับจัดการ DOM
- กลุ่มเป้าหมายที่รองรับคือเบราว์เซอร์ IE11+
- เป้าหมายไม่ใช่การนำฟีเจอร์ทั้งหมดของ jQuery มาทำซ้ำเหมือนเดิม แต่ฟีเจอร์ที่ Cash นำมาใช้งานถูกออกแบบให้เข้ากันได้กับ jQuery API เป็นส่วนใหญ่
- ผู้ใช้ที่ย้ายมาจาก jQuery สามารถดู migration guide ได้
เปรียบเทียบขนาดและฟีเจอร์
- ในการเปรียบเทียบขนาดไฟล์ Cash มีขนาดเล็กกว่า Zepto 1.2.0 และ jQuery Slim 3.4.1
- Unminified: 36.5KB
- Minified: 16KB
- Minified & Gzipped: 6KB
- jQuery Slim 3.4.1 มีขนาด 24.4KB เมื่อ minified & gzipped ส่วน Cash ลดขนาดลงได้ 76.6% เมื่อเทียบกัน
- หากต้องการบันเดิลที่เล็กกว่านี้ สามารถใช้ partial builds ได้
- จากการเปรียบเทียบฟีเจอร์ Cash รองรับเบราว์เซอร์สมัยใหม่, มีการบำรุงรักษาอย่างต่อเนื่อง, รองรับ อีเวนต์แบบ namespaced, codebase แบบ TypeScript และ TypeScript types ที่สร้างจากโค้ด
- ใน Cash นั้น Partial builds สามารถตัดเมธอดรายตัวออกได้ ส่วน Zepto และ jQuery Slim ระบุว่าเป็นการตัดออกในระดับโมดูลทั้งหมด
วิธีใช้งาน
- สามารถโหลด Cash จาก jsDelivr และใช้งานบนเบราว์เซอร์ได้ทันที
<script src="https://cdn.jsdelivr.net/npm/cash-dom/…;
<script>
$(function () {
$('html').addClass ( 'dom-loaded' );
$('<footer>Appended with Cash</footer>').appendTo ( document.body );
});
</script>
npm install --save cash-dom
import $ from "cash-dom";
$(function () {
$('html').addClass ( 'dom-loaded' );
$('<footer>Appended with Cash</footer>').appendTo ( document.body );
});
โครงสร้าง API
$() คือ เมธอด selector หลักของ Cash และคืนค่าคอลเลกชันของโหนดที่สามารถจัดการได้
- หากส่งฟังก์ชันเข้าไป ฟังก์ชันนั้นจะถูกเรียกเมื่อ DOM พร้อมใช้งาน
$() รับ selector, DOM node, nodeList, HTML string, Cash collection และ callback เมื่อ document ready ได้
- Cash มี API หลัก ๆ สามประเภท
- query selector
- เมธอดคอลเลกชัน
- เมธอดไลบรารีของอ็อบเจ็กต์
$ แบบ global
เมธอดคอลเลกชัน
- เมธอดคอลเลกชันจะถูกเรียกหลังจากสร้างคอลเลกชันด้วย
$() เช่น $(element).addClass(className)
- หมวดหมู่ที่มีให้แบ่งเป็น แอตทริบิวต์, คอลเลกชัน, CSS, ข้อมูล, มิติ, เอฟเฟกต์, อีเวนต์, ฟอร์ม, การจัดการ DOM, offset และการนำทาง
- เมธอดหลัก ๆ มีดังนี้
- คลาส/แอตทริบิวต์:
addClass, removeClass, toggleClass, attr, prop, removeAttr
- การจัดการคอลเลกชัน:
add, each, eq, filter, first, get, map, slice
- การจัดการ DOM:
append, prepend, before, after, html, text, clone, remove, replaceWith, wrap
- อีเวนต์:
on, off, one, ready, trigger
- การนำทาง:
find, children, closest, parent, parents, siblings, next, prev
- มี extra methods บางส่วนให้ใช้ แต่ถูกปิดไว้เป็นค่าเริ่มต้น
$.fn คือ prototype หลักของคอลเลกชัน และสามารถเพิ่มเมธอดกำหนดเองให้กับทุกคอลเลกชันได้เหมือนปลั๊กอิน
เมธอด Cash แบบ global
- อ็อบเจ็กต์
$ แบบ global มีเมธอดตรวจสอบชนิดและ utility รวมอยู่ด้วย
- เมธอดตรวจสอบชนิดมี
$.isArray, $.isFunction, $.isNumeric, $.isPlainObject, $.isWindow
- utility มี
$.guid, $.each, $.extend, $.parseHTML, $.unique
$.extend ขยายอ็อบเจ็กต์เป้าหมายด้วยพร็อพเพอร์ตีของอ็อบเจ็กต์ต้นทาง และรองรับ deep extend ด้วย
$.parseHTML คืนค่าคอลเลกชันจากสตริง HTML และ $.unique คืนค่าอาร์เรย์ใหม่ที่ลบค่าซ้ำออกแล้ว
การขยายและการมีส่วนร่วม
- Cash สามารถขยายด้วยเมธอดกำหนดเองได้ โดยมีวิธีขยายอธิบายไว้ใน extending Cash
- สามารถเปิดปัญหาหรือคำขอฟีเจอร์ได้ผ่าน GitHub issue
- workflow สำหรับ Pull request คือ clone repository, ติดตั้ง dependencies, ใช้
npm run dev เพื่อ recompile อัตโนมัติ, รันเทสต์ด้วย npm run test และอัปเดต README หากจำเป็น
- ไลเซนส์คือ MIT
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ทุกวันนี้เบราว์เซอร์ดีขึ้นมาก จนในหลายกรณีแค่ alias สองบรรทัด ด้านล่างก็พอสำหรับทำให้การจัดการ DOM ง่ายขึ้นแล้ว
dqs = document.querySelector.bind(document);dqsA = document.querySelectorAll.bind(document);ดังนั้นจึงใช้
dqs('#country')แทนdocument.querySelector('#country')และdqsA('.city')แทนdocument.querySelectorAll('.city')ได้ส่วนที่เหลือใช้ ฟังก์ชันเนทีฟของเบราว์เซอร์ ไปก็ได้ และโดยปกติก็มัก import จากโมดูลแบบ
import { dqs, dqsA } from '/lib/js/dqs.js';https://github.com/no-gravity/dqs.js
querySelector/querySelectorAllเองนั้นดูมีประโยชน์และสมเหตุสมผล แต่การเอามาใช้ผ่านimportดูเกินจำเป็นมันเป็นแค่ สองบรรทัดง่าย ๆ ไม่มีเหตุผลต้องทำเป็น dependency แค่คัดลอกมาแปะก็พอ
ตัวอย่างเช่น
$.fn.one()หรือ$.fn.on()ที่จัดการหลาย event ใช้ผ่าน jQuery/Cash จะง่ายกว่า และถ้าดู implementation ภายในก็มีงานอยู่ไม่น้อย: https://github.com/fabiospampinato/cash/blob/master/src/even...querySelectorAll()ไม่ใช่ live collection เลยมักแปลงเป็น array ทันทีแบบนี้dqsA = s => Array.from(document.querySelectorAll(s));แบบนี้จะใช้เมธอดของ array อย่าง
.map()หรือ.filter()กับผลลัพธ์ได้ทันที ให้ความรู้สึกคล้าย jQueryเช่น ระหว่างทดสอบ ใน Safari event
selectของบาง element ไม่เกิดขึ้นเลย และในบางเบราว์เซอร์ ถ้าแค่ย้าย caret ก็ไม่มี event เกิดขึ้นต่อให้ไม่ต้องใช้ฟีเจอร์ของ React อย่าง component, state, props ฟังก์ชันเนทีฟของเบราว์เซอร์อย่างเดียวก็ยังไม่พอ และเห็นคุณค่าของ React DOM ที่ช่วยกลบความต่างระหว่างเบราว์เซอร์
หลังจากถอด polyfill ออกไปเกือบหมดแล้ว ข้อดีที่ยังคงอยู่ของ jQuery คือ การจัดการ list อัตโนมัติ
ความสามารถในการยกเลิกการเลือกปุ่มทั้งหมดใน form ด้วยการเรียกครั้งเดียวยังหาตัวอื่นเทียบได้ยาก และ parent query ก็เช่นกัน
แต่ปัญหาใหญ่ที่สุดในเชิง implementation คือเมื่อ list ว่าง มันจะล้มเหลวแบบเงียบ ๆ
ผมเคยแก้บั๊กแบบนี้มาเยอะมาก ตอน refactor DOM tree เพื่อจุดประสงค์ด้าน layout ในภายหลัง ดังนั้นถ้าจะสร้าง jQuery ใหม่ตอนนี้ ค่าเริ่มต้นควร error เมื่อเจอชุดว่าง และมี chain call หรือ flag ให้ล้มเหลวแบบเงียบ ๆ เฉพาะเวลาที่ไม่สนใจจริง ๆ เท่านั้น
เมื่อก่อนเคยใช้เวลาหลายชั่วโมงดูว่าจะดึง Sizzle ออกแล้วเปลี่ยนเป็นแบบนี้ได้ไหม แต่ก็ไม่ได้ทำต่อ
ท้ายที่สุด jQuery ก็เชื่อมโยงกับข้อถกเถียงเก่าเรื่อง library vs framework ด้วย และหลังจากทำ single-page app ด้วย framework ขนาดใหญ่มานาน ก็รู้สึกเหมือนใกล้กลับเข้าสู่หุบเขาแห่งความผิดหวังอีกครั้ง
ถ้าสั่งให้ซ่อน
.fooทั้งหมด ถ้ามีก็ซ่อน ถ้าไม่มีก็ไม่เกิดอะไร เป็นแนว fire and forget คล้าย CSSถ้าเขียน
.foo { color: red; }แล้วในเอกสารไม่มี.fooก็ไม่มี side effect นอกจาก overhead เล็กน้อยdocument.querySelectorAll('input[type=checkbox]').forEach((i) => i.checked = false);นี่เป็นวิธีที่ใช้
NodeListที่วนซ้ำได้และตัวช่วย iteratorparent query จำนวนมากจัดการได้ด้วย
element.closest()โค้ดที่ใช้ jQuery ต้องอาศัยให้นักพัฒนาทุกคนรู้ selector ทุกตัวในโปรเจกต์ แล้วคอยอัปเดตเมื่อ DOM เปลี่ยน ซึ่งแน่นอนว่าเป็นไปไม่ได้
ดูเหมือนจะเป็นไปได้ที่จะเปลี่ยนไปใช้ฟังก์ชัน initialization ของ jQuery ที่ implement เอง เพื่อบังคับตรวจสอบความยาว
ในสถานการณ์ที่เว็บไซต์กระแสหลักปล่อย JavaScript เป็น ระดับเมกะไบต์ จริง ๆ ผมไม่เข้าใจว่าทำไมต้องเขียน library ที่มีฟีเจอร์น้อยกว่าใหม่ทั้งก้อนเพื่อประหยัด 50KB
หลายบทความคัดค้านการใช้ jQuery เพราะขนาดแพ็กเกจและข้อจำกัดด้านแบนด์วิดท์ แต่ในขณะเดียวกันกลับสนับสนุน SPA framework ที่ใช้แบนด์วิดท์มากกว่ามาก
เป็นการให้เหตุผลแบบ cargo cult ที่ไร้สาระโดยสิ้นเชิง
ทั้งสามกรณีมีคำตอบเหมือนกัน: ของที่ใหญ่นั้นไม่ใช่ผม และผมกำลังทำสิ่งอื่นที่เล็กกว่า
อีกคำตอบหนึ่งคือ ถ้าสิ่งที่ใหญ่เกินไปเป็นปัญหา การทำให้เล็กลง ก็ดูเหมือนเป็นทางแก้
สุดท้ายแล้วคำถามที่อยากถามคงเป็นว่าทำไมนักพัฒนาถึงใช้เวลาเขียน library ใหม่ แต่เรื่องนั้นก็ไม่น่าตกใจนัก
งานเขียนโปรแกรมจำนวนมากคือการเขียนสิ่งที่มีอยู่แล้วใหม่ เพราะงานจำเป็นต้องใช้ ต้องการพฤติกรรมหรือคุณลักษณะด้าน performance ที่ต่างออกไปเล็กน้อย หรือแค่อยากเรียนรู้ว่ามันทำงานอย่างไร
หากกำลังมองหาทางเลือกแทน jQuery ผมรอ jQuery 4.0 นานเกินไป สุดท้ายเลยทำอะไรที่คล้าย jQuery ขึ้นมาเองโดยมีความแตกต่างสำคัญอยู่บ้าง
แอนิเมชัน, tween และ timeline ใช้ CSS ล้วนแทนระบบคัสตอมของ jQuery, จัดการทั้งอิลิเมนต์เดี่ยวและลิสต์ได้อย่างโปร่งใส และมุ่งให้ใช้แบบ inline
me()ระบุว่าจะคืนค่าอิลิเมนต์ 1 ตัว หรืออิลิเมนต์แรก หรือnullส่วนany()ระบุว่าจะคืนค่าเป็นอาร์เรย์หรืออาร์เรย์ว่างแต่ตัวอย่างด้านล่างกลับสื่อว่าอาจเป็น
nullได้ เช่นany('button')?.forEach(...),any('button')?.map(...)เลยสับสนว่า
any()คืนค่าเป็นอาร์เรย์เสมอตามคำอธิบายข้างต้น หรือสามารถเป็นnullได้เหมือนในตัวอย่างด้านล่างสนใจเป็นพิเศษในเรื่อง locality of behavior
อยากรู้ว่าประสบการณ์ในการใช้
currentScript.parentElementเป็นอย่างไรตอนที่สำรวจคร่าว ๆ เมื่อเดือนที่แล้ว ผมได้ความรู้สึกว่าน่าจะไม่น่าเชื่อถือในเคสบางมุม แต่จำไม่ได้แน่ชัดว่าเป็นเมื่อไหร่
ยังไม่ได้ขุดลึก และดีใจที่ทำให้มันทำงานได้ดี
ถ้าอยู่บนสมมติฐานว่าไม่ใช่
asyncหรือmoduleต่อให้โหลดสคริปต์ติดกัน 3 ตัวcurrentScript.parentElementก็น่าจะยังทำงานได้ในทุกเบราว์เซอร์ไม่ใช่หรือSvelteKit ก็เคยถกเรื่องนี้ และสุดท้ายก็ implement ID แบบสุ่มเพื่อระบุอิลิเมนต์เป้าหมาย: https://github.com/sveltejs/kit/issues/2221
จากการดูคู่มือ migration ทำให้ได้รู้ฟีเจอร์บางอย่างที่ jQuery ทำได้แต่ Cash ทำไม่ได้ ซึ่งผมไม่เคยรู้มาก่อนและสักวันอาจน่าลองใช้
https://github.com/fabiospampinato/cash/blob/master/docs/mig...
ในฐานะเป้าหมายเสริม ถ้าใช้เวทมนตร์ของ template string ใน TypeScript เพื่อ infer ชนิดของอิลิเมนต์ได้อย่างแม่นยำก็คงดี
ตัวอย่างเช่นสามารถ infer แบบ static ได้ว่า
$('div#name')คือHTMLDivElementtyped-query-selectorตัวอย่างการใช้งานจริงอยู่ที่นี่: https://github.com/GoogleChrome/lighthouse/blob/main/types/i...
ไม่รู้ว่า TypeScript ทำได้หรือเปล่า และก็มองไม่ค่อยออกว่าจะทำอย่างไร
ได้ยินมาว่า jQuery 4 เป็น ทางเลือกแทน jQuery สำหรับเบราว์เซอร์สมัยใหม่
ตอนแรกผมใช้ตัวนี้ใน browser extension ที่กำลังทำอยู่ แต่สุดท้ายก็ย้ายไปใช้ ไลบรารี JSX
พอเกินขอบเขตของ “แอปง่าย ๆ” ไปแล้ว jQuery จะกลายเป็นโค้ดที่อนุมานตามได้ยากอย่างรวดเร็ว และในฐานะคนที่เคยทำไลบรารีที่ได้แรงบันดาลใจจาก jQuery เองก็รู้สึกแบบนั้นเหมือนกัน
สุดท้ายก็ต้องใช้เครื่องมือให้เหมาะกับงาน
[1]: https://github.com/aleclarson/dough
ถ้าคุณรับมือกับ jQuery ในแอปขนาดกลางถึงใหญ่ได้ดีก็โอเค แต่มันไม่ใช่รสนิยมของผม
เมื่อก่อนตอนพยายามลด JS ผมเคยใช้ https://github.com/filamentgroup/shoestring
เหตุผลหลักคือมันมี custom build ที่ใส่เฉพาะสิ่งที่จำเป็นจริง ๆ
Cash ก็ดูเหมือนจะมีฟีเจอร์คล้ายกัน แต่ถูกซ่อนไว้ในเอกสารมากกว่านิดหน่อย: https://github.com/fabiospampinato/cash/blob/master/docs/par...
ถ้าจะใช้ ผมน่าจะลองทางนั้นก่อน
ถึงอย่างนั้นก็ยังคิดว่าการใช้สิ่งที่เบราว์เซอร์ยุคนี้มีให้โดยตรงน่าจะเป็นตัวเลือกที่ดีกว่า
ของจริงค่อนข้างดี และ jQuery ก็ไม่จำเป็นอีกต่อไปแล้ว
โดยเฉพาะเมื่อเห็นว่าแม้แต่ทางเลือกแทน jQuery ขนาดเล็กก็ยัง 6kB ขณะที่ Preact ซึ่งเป็นไลบรารีคล้าย React มีขนาดแค่ครึ่งหนึ่งของมัน
ไม่ค่อยแน่ใจว่ามันช่วยได้มากไปกว่าการตั้ง alias ให้ Web API ที่มีอยู่แล้วหรือไม่