- Cash là một thư viện thay thế jQuery rất nhỏ gọn, cung cấp cú pháp kiểu jQuery để thao tác DOM trên các trình duyệt hiện đại hỗ trợ IE11+
- Thư viện tận dụng các tính năng của trình duyệt hiện đại để giảm kích thước codebase, đồng thời cho phép dùng các phương thức chaining quen thuộc với kích thước tệp nhỏ hơn nhiều
- Dù không đặt mục tiêu tương đương 100% tính năng với jQuery, thư viện vẫn bao phủ phần lớn các trường hợp sử dụng hằng ngày và API được triển khai nhìn chung tương thích với jQuery
- Kích thước ở bản minified & gzipped là 6KB, nhỏ hơn 76,6% so với 24.4KB của jQuery Slim 3.4.1, và còn có thể giảm thêm bằng partial builds
- Thư viện hỗ trợ codebase TypeScript, kiểu TypeScript được sinh từ mã nguồn, namespace event, và partial build cho phép loại bỏ từng phương thức riêng lẻ
Vấn đề mà Cash giải quyết
- Cash là lựa chọn thay thế jQuery cho các trình duyệt hiện đại, cung cấp bộ chọn
$() kiểu jQuery và các phương thức collection có thể chaining để thao tác DOM
- Mục tiêu hỗ trợ là các trình duyệt IE11+
- Dù không nhằm triển khai toàn bộ tính năng của jQuery, các tính năng mà Cash có vẫn được thiết kế để phần lớn tương thích với API của jQuery
- Người dùng chuyển từ jQuery sang có thể xem migration guide
So sánh kích thước và tính năng
- Trong so sánh kích thước tệp, Cash nhỏ hơn Zepto 1.2.0 và jQuery Slim 3.4.1
- Unminified: 36.5KB
- Minified: 16KB
- Minified & Gzipped: 6KB
- jQuery Slim 3.4.1 có kích thước 24.4KB ở bản minified & gzipped, và Cash giúp giảm 76,6% kích thước so với mức này
- Nếu cần bundle nhỏ hơn nữa, có thể dùng partial builds
- Về mặt tính năng, Cash cung cấp hỗ trợ trình duyệt hiện đại, được bảo trì tích cực, namespace event, codebase TypeScript, và kiểu TypeScript được sinh từ mã nguồn
- Partial builds của Cash cho phép loại bỏ từng phương thức riêng lẻ, trong khi Zepto và jQuery Slim được đánh dấu là loại bỏ theo toàn bộ module
Cách sử dụng
- Có thể tải Cash từ jsDelivr và dùng trực tiếp trong trình duyệt
<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 );
});
Cấu trúc API
$() là phương thức selector cốt lõi của Cash và trả về một collection node có thể thao tác
- Khi truyền vào một hàm, hàm đó sẽ được chạy tại thời điểm DOM sẵn sàng
$() có thể nhận selector, DOM node, nodeList, HTML string, Cash collection, và document ready callback
- Cash cung cấp ba nhóm API chính
- Query selector
- Phương thức collection
- Các phương thức thư viện của đối tượng
$ toàn cục
Phương thức collection
- Các phương thức collection được gọi sau khi tạo collection bằng
$(), theo cách như $(element).addClass(className)
- Các nhóm chức năng được cung cấp gồm thuộc tính, collection, CSS, dữ liệu, kích thước, hiệu ứng, sự kiện, form, thao tác DOM, offset và duyệt cây
- Các phương thức chính gồm
- Class/thuộc tính:
addClass, removeClass, toggleClass, attr, prop, removeAttr
- Xử lý collection:
add, each, eq, filter, first, get, map, slice
- Thao tác DOM:
append, prepend, before, after, html, text, clone, remove, replaceWith, wrap
- Sự kiện:
on, off, one, ready, trigger
- Duyệt cây:
find, children, closest, parent, parents, siblings, next, prev
- Có một số extra methods, nhưng mặc định bị vô hiệu hóa
$.fn là prototype chính của collection, cho phép thêm các phương thức tùy chỉnh vào mọi collection theo kiểu plugin
Phương thức Cash toàn cục
- Đối tượng
$ toàn cục bao gồm các phương thức kiểm tra kiểu và utility
- Các phương thức kiểm tra kiểu gồm
$.isArray, $.isFunction, $.isNumeric, $.isPlainObject, $.isWindow
- Các utility gồm
$.guid, $.each, $.extend, $.parseHTML, $.unique
$.extend mở rộng đối tượng đích bằng các thuộc tính của đối tượng nguồn, và cũng hỗ trợ mở rộng sâu
$.parseHTML trả về collection từ chuỗi HTML, còn $.unique trả về một mảng mới đã loại bỏ phần tử trùng lặp
Mở rộng và đóng góp
- Cash có thể được mở rộng bằng các phương thức tùy chỉnh, và cách mở rộng được tổng hợp trong extending Cash
- Có thể tạo yêu cầu lỗi hoặc tính năng qua GitHub issue
- Quy trình làm pull request gồm clone kho mã, cài dependency, tự động biên dịch lại bằng
npm run dev, chạy kiểm thử bằng npm run test, và cập nhật README nếu cần
- Giấy phép là MIT
1 bình luận
Ý kiến trên Hacker News
Trình duyệt ngày nay đã tốt hơn nhiều, nên để đơn giản hóa thao tác DOM, thường chỉ cần hai dòng alias dưới đây là đủ
dqs = document.querySelector.bind(document);dqsA = document.querySelectorAll.bind(document);Vì vậy có thể dùng như
dqs('#country')thay chodocument.querySelector('#country'), vàdqsA('.city')thay chodocument.querySelectorAll('.city')Phần còn lại cứ dùng hàm native của trình duyệt cũng ổn, và thường import từ module kiểu
import { dqs, dqsA } from '/lib/js/dqs.js';https://github.com/no-gravity/dqs.js
querySelector/querySelectorAllcó vẻ hữu ích và hợp lý, nhưng đưa chúng vào bằngimportthì hơi quáChỉ là hai dòng đơn giản thôi, không có lý do gì để biến thành dependency; cứ copy-paste là được
Ví dụ
$.fn.one()hay$.fn.on()để xử lý nhiều event thì dùng bằng jQuery/Cash dễ hơn, và nhìn implementation bên trong sẽ thấy khá nhiều việc: https://github.com/fabiospampinato/cash/blob/master/src/even...querySelectorAll()không phải là live collection, nên thường chuyển ngay sang mảng như thế nàydqsA = s => Array.from(document.querySelectorAll(s));Làm vậy có thể dùng trực tiếp các array method như
.map()hay.filter()trên kết quả, nên vẫn còn cảm giác gần giống jQueryChẳng hạn trong lúc test, trên Safari event
selectcủa một phần tử hoàn toàn không phát sinh, còn ở một số trình duyệt thì khi chỉ di chuyển caret cũng không có eventDù không cần các tính năng React như component, state, props, chỉ dùng hàm native của trình duyệt vẫn chưa đủ; giá trị của React DOM trong việc che lấp khác biệt trình duyệt đã hiện rõ
Sau khi gần như gỡ hết polyfill, lợi thế bền vững còn lại của jQuery theo tôi là tự động xử lý danh sách
Khả năng bỏ chọn mọi nút trong một form chỉ bằng một lần gọi vẫn khó có thứ khác bắt kịp, và truy vấn phần tử cha cũng vậy
Tuy nhiên vấn đề lớn nhất về mặt implementation là khi danh sách rỗng thì nó thất bại một cách im lặng
Vì đã phải sửa quá nhiều bug kiểu này phát sinh khi về sau refactor cây DOM cho mục đích layout, nên nếu bây giờ làm lại jQuery, mặc định tôi sẽ cho báo lỗi với tập rỗng, và chỉ thất bại im lặng qua chain call hoặc flag khi thật sự không quan tâm
Trước đây tôi từng dành vài giờ xem liệu có thể tách Sizzle ra và đổi theo kiểu này không, nhưng rồi không đi tiếp
Rốt cuộc jQuery cũng liên quan đến cuộc tranh luận cũ library vs framework, và sau thời gian dài làm ứng dụng một trang bằng các framework khổng lồ, tôi có cảm giác lại sắp đến gần “thung lũng vỡ mộng”
Nếu ra lệnh ẩn tất cả
.foothì có sẽ ẩn, không có thì chẳng có gì xảy ra; kiểu fire and forget này giống CSSViết
.foo { color: red; }mà trong tài liệu không có.foothì ngoài chút overhead nhỏ cũng không có tác dụng phụdocument.querySelectorAll('input[type=checkbox]').forEach((i) => i.checked = false);Đây là cách tận dụng
NodeListcó thể duyệt và iterator helperNhiều truy vấn phần tử cha có thể xử lý bằng
element.closest()Code dựa trên jQuery đòi hỏi mọi developer phải biết tất cả selector trong dự án rồi cập nhật khi DOM thay đổi, điều này hiển nhiên là bất khả thi
Có vẻ có thể thay bằng một implementation tự viết cho hàm khởi tạo jQuery để buộc kiểm tra độ dài
Trong bối cảnh các website phổ biến đang phát tán đúng nghĩa JavaScript tính bằng megabyte, tôi không hiểu vì sao lại phải viết lại cả một thư viện ít tính năng hơn chỉ để tiết kiệm 50KB
Nhiều bài viết phản đối dùng jQuery vì kích thước package và hạn chế băng thông, đồng thời lại bênh vực SPA framework dùng băng thông nhiều hơn hẳn
Đó đúng là kiểu suy luận cargo cult hoàn toàn vô lý
Cả ba trường hợp đều có cùng câu trả lời: thứ lớn kia không phải là tôi, còn tôi đang làm một thứ khác nhỏ hơn
Một câu trả lời khác là, nếu quá lớn là vấn đề, thì trở nên nhỏ hơn nghe giống một giải pháp
Cuối cùng có vẻ điều muốn hỏi là vì sao developer lại dành thời gian viết lại thư viện, nhưng chuyện đó cũng không đáng ngạc nhiên đến vậy
Một phần đáng kể của lập trình là viết lại những thứ đã có, vì nhu cầu công việc, vì cần hành vi hoặc đặc tính hiệu năng hơi khác, hoặc đơn giản vì muốn học cách nó hoạt động
Nếu đang tìm một lựa chọn thay thế jQuery, vì chờ jQuery 4.0 quá lâu nên cuối cùng tôi đã tự làm một thứ giống jQuery với vài khác biệt quan trọng
Animation, tween và timeline dùng CSS thuần thay vì hệ thống tùy biến của jQuery; xử lý minh bạch cả phần tử đơn lẻ lẫn danh sách; và hướng tới cách dùng inline
me()được nói là trả về 1 phần tử, hoặc phần tử đầu tiên, hoặcnull; cònany()được nói là trả về một mảng hoặc mảng rỗngNhưng các ví dụ bên dưới như
any('button')?.forEach(...),any('button')?.map(...)lại hàm ý rằng nó cũng có thể lànullTôi thấy khó hiểu không biết
any()có luôn trả về mảng như mô tả ở trên hay cũng có thể lànullnhư ví dụ bên dướiTôi đặc biệt quan tâm đến tính cục bộ của hành vi
Tôi tò mò trải nghiệm dùng
currentScript.parentElementra saoKhi khảo sát nhanh tháng trước, tôi có ấn tượng rằng có lẽ nó không đáng tin trong một số trường hợp ngách, nhưng không nhớ chính xác là khi nào
Tôi chưa đào sâu, và rất vui khi thấy có người làm cho nó hoạt động tốt
Nếu giả định không phải
asynchaymodule, thì kể cả tải 3 script liên tiếp, chẳng phảicurrentScript.parentElementvẫn nên hoạt động trên mọi trình duyệt sao?SvelteKit cũng từng bàn chuyện này, và cuối cùng đã triển khai ID ngẫu nhiên để chỉ định phần tử đích: https://github.com/sveltejs/kit/issues/2221
Khi xem hướng dẫn migration, tôi học được vài tính năng jQuery làm được nhưng Cash thì không; trước đây tôi chưa biết và có thể một ngày nào đó sẽ thử dùng
https://github.com/fabiospampinato/cash/blob/master/docs/mig...
Như một mục tiêu mở rộng, sẽ rất hay nếu dùng phép màu template string của TypeScript để suy luận chính xác kiểu phần tử
Ví dụ có thể suy luận tĩnh rằng
$('div#name')làHTMLDivElementtyped-query-selectorVí dụ sử dụng thực tế ở đây: https://github.com/GoogleChrome/lighthouse/blob/main/types/i...
Tôi không biết TypeScript có làm được không, và cũng chưa thấy rõ có thể làm bằng cách nào
Tôi nghe nói jQuery 4 là lựa chọn thay thế jQuery dành cho các trình duyệt hiện đại
Ban đầu tôi dùng cái này trong extension trình duyệt đang làm, nhưng cuối cùng đã chuyển sang một thư viện JSX
Khi vượt ra khỏi phạm vi “ứng dụng đơn giản”, jQuery rất nhanh trở thành mã khó suy luận, và với tư cách người từng tự làm một thư viện lấy cảm hứng từ jQuery, tôi cũng cảm thấy vậy
Rốt cuộc vẫn phải dùng công cụ phù hợp với công việc
[1]: https://github.com/aleclarson/dough
Nếu bạn có thể xử lý tốt jQuery trong ứng dụng vừa và lớn thì ổn, nhưng không hợp gu của tôi
Trước đây khi cố giảm JS, tôi từng dùng https://github.com/filamentgroup/shoestring
Lý do chính là nó cung cấp custom build chỉ gồm những gì thật sự cần
Cash dường như cũng có tính năng tương tự, nhưng trong tài liệu thì hơi bị giấu kỹ hơn: https://github.com/fabiospampinato/cash/blob/master/docs/par...
Nếu dùng, có lẽ tôi sẽ thử hướng đó trước
Dù vậy, tôi vẫn nghĩ dùng trực tiếp những gì trình duyệt ngày nay cung cấp là lựa chọn tốt hơn
Thực tế chúng khá tốt, và jQuery không còn thực sự cần thiết nữa
Điều này càng rõ hơn khi ngay cả một lựa chọn thay thế jQuery nhỏ cũng đã 6kB, trong khi Preact, một thư viện kiểu React, chỉ bằng một nửa kích thước đó
Tôi không chắc nó giúp được gì nhiều hơn việc đặt alias cho các Web API đã có sẵn