
Web Development Essentials 035.3: Khái niệm cơ bản về SQL
Tạo bảng SQLite, hiểu khóa chính và CHECK, dùng CRUD có WHERE và bind tham số từ Node.js; kiểm tra transaction rollback, mở lại dữ liệu và API driver.

Hiểu const, let, scope, undefined, null và NaN; chuyển đổi đầu vào có kiểm tra, dùng array chứa object và phân biệt alias với bản sao nông trong lab DEMO.
Một biểu mẫu nhận số lượng máy cần hỗ trợ là “2”. Khi cộng thêm 1, chương trình hiển thị “21”. Mã không hỏng cú pháp: toán tử cộng đang nối chuỗi. Một lỗi khác khó thấy hơn là sửa bản sao yêu cầu nhưng dữ liệu gốc cũng đổi vì hai biến cùng tham chiếu một object. Kiểu giá trị và cấu trúc dữ liệu quyết định các hành vi này.
Bài 034.2, Cấu trúc Dữ liệu JavaScript, tham chiếu Web Development Essentials 030-100 trang in 221–237, được viết lại độc lập và cập nhật nguồn ngày 23/10/2026. Bạn cần chạy được JavaScript trong module hoặc Console. Kết quả cần đạt là khai báo binding có phạm vi rõ, nhận biết kiểu dữ liệu, chuyển đổi đầu vào có kiểm tra và biểu diễn một danh sách yêu cầu bằng array chứa object.
const tạo binding không thể gán lại sau khi khởi tạo; let dùng khi cần gán lại. Một khai báo const thông thường cần giá trị khởi tạo tại vị trí thực thi khai báo, không phải mọi const đều được gán khi chương trình vừa bắt đầu. Cả let và const có phạm vi khối. Var trong mã cũ có phạm vi hàm hoặc phạm vi script thích hợp, không bị giới hạn bởi một khối if đơn thuần.
const ticket = { id: "DEMO-55", count: 2 };
ticket.count += 1; // Hợp lệ: sửa thuộc tính của object.
let retries = 0;
retries += 1;
console.log(ticket.count, retries);
Gán ticket = { count: 0 } sẽ gây TypeError vì cố thay binding, khác với đổi ticket.count. Const không biến mọi dữ liệu bên trong thành bất biến. Tên viết hoa như MAX_RETRIES là quy ước cho một số hằng cấu hình, không phải cơ chế bảo vệ của ngôn ngữ.
Khai báo rõ tên trước khi dùng. Gán cho tên chưa khai báo không tương đương var: trong module và strict mode nó gây ReferenceError. Đừng dựa vào việc một classic script cũ vô tình tạo thuộc tính toàn cục. Một tên ở mức trên cùng của module có phạm vi module; nó không mặc nhiên là biến dùng được từ mọi script trên trang.
Tên phân biệt hoa thường và có quy tắc cú pháp, bao gồm khả năng dùng $, _ và nhiều ký tự Unicode hợp lệ. Với mã học tập và cộng tác, tên ASCII rõ vai trò như ticketCount thường dễ gõ và kiểm tra hơn tận dụng toàn bộ khả năng của cú pháp. Tránh đặt tên giống API có sẵn rồi vô tình che chúng.
JavaScript có kiểu động: một binding let có thể được gán các giá trị thuộc kiểu khác nhau theo thời gian. Điều đó không có nghĩa không cần quan tâm kiểu. Một phép toán có thể chuyển đổi ngầm, từ chối giá trị hoặc cho kết quả khác ý định. Đừng đổi cùng một biến từ số sang object chỉ vì ngôn ngữ cho phép nếu điều đó khiến mã khó giải thích.
Các kiểu nguyên thủy là undefined, null, boolean, number, bigint, string và symbol. Object là nhóm còn lại, bao gồm array và nhiều dạng đối tượng khác. Boolean có true/false; string biểu diễn văn bản. Symbol tạo giá trị duy nhất, có thể dùng làm khóa thuộc tính; bài này chỉ cần nhận biết chứ chưa thiết kế giao thức dựa trên symbol.
Number sử dụng biểu diễn số dấu phẩy động nhị phân và có cả NaN, Infinity. Các phép toán thập phân như 0.1 + 0.2 có thể không bằng chính xác 0.3. Với tiền, cần quy tắc biểu diễn và làm tròn phù hợp; ví dụ một số hệ thống dùng số nguyên theo đơn vị nhỏ nhất trong giới hạn an toàn. BigInt biểu diễn số nguyên lớn, nhưng không thay thế tùy tiện cho Number và không trộn hai kiểu trong phép cộng thông thường.
TypeScript bổ sung hệ thống kiểu phục vụ kiểm tra tĩnh cho JavaScript; nó không phải ngôn ngữ cấp thấp và khai báo kiểu không tự kiểm chứng dữ liệu mạng ở runtime. Loạt Essentials này ưu tiên hiểu JavaScript trước, chỉ nhắc TypeScript để tránh đọc sai tài liệu cũ.
let value; khởi tạo value thành undefined khi khai báo đã được thực thi. Một thuộc tính không tồn tại thường trả undefined khi đọc. Null thường được ứng dụng dùng để biểu thị thiếu đối tượng hoặc giá trị một cách chủ ý theo quy ước. Không nói “chưa gán thì luôn null”. Đọc trực tiếp một tên hoàn toàn chưa khai báo còn có thể gây ReferenceError.
typeof hữu ích nhưng có các trường hợp lịch sử và giới hạn. typeof null trả chuỗi “object”, dù null là nguyên thủy. typeof [] cũng trả “object”; dùng Array.isArray để nhận biết array. typeof missingName có thể trả “undefined” cho tên chưa khai báo, nhưng typeof một binding let/const còn trong temporal dead zone của chính scope vẫn gây ReferenceError. Đừng dùng mẹo đầu tiên để kết luận mọi tên đều truy cập an toàn.
NaN là giá trị thuộc kiểu number thể hiện kết quả số không hợp lệ trong nhiều tình huống. NaN không bằng chính nó theo phép so sánh nghiêm ngặt; dùng Number.isNaN để kiểm tra trực tiếp. Khi muốn chấp nhận số hữu hạn, Number.isFinite còn loại cả Infinity. Nó không chuyển chuỗi “2” thành số trước khi kiểm tra như hàm isFinite toàn cục.

Dấu = gán giá trị. Các phép +, -, *, / thực hiện theo quy tắc chuyển đổi tương ứng; % là phép lấy phần dư trong JavaScript, không luôn trùng định nghĩa modulo toán học khi có số âm. Dùng ngoặc để thể hiện nhóm phép tính. += cập nhật binding hoặc thuộc tính, nhưng với chuỗi nó cũng có thể nối văn bản.
"2" + 1 là “21”; "2" * 3 cho số 6 vì phép nhân chuyển đổi phù hợp. Chuỗi không chuyển được thành số có thể dẫn tới NaN. Vì vậy, phát biểu “nhân có chuỗi luôn ra NaN” là sai. Toán tử hậu tố tăng trả giá trị cũ của biểu thức, còn tiền tố trả giá trị sau khi tăng; với người mới, tách phép cập nhật khỏi biểu thức lớn thường dễ đọc hơn.
Chuyển đổi Number không tự xác nhận quy tắc nghiệp vụ. Number("") và Number của chuỗi chỉ có khoảng trắng cho 0; Number của null cũng cho 0. Nếu biểu mẫu yêu cầu một số nguyên từ 1 đến 20, phải kiểm tra đầu vào rỗng trước, sau đó kiểm tra số nguyên và miền giá trị. Cả hai toán hạng đều cần được hiểu đúng; chỉ đổi toán hạng đầu sang số không ngăn toán hạng sau là chuỗi gây nối.
So sánh nghiêm ngặt === giúp tránh một số chuyển đổi ngầm khi kiểm tra kết quả, nhưng không thay validation. Các bài hàm và điều kiện sau sẽ đóng gói quy trình kiểm tra; ở đây quan sát từng bước để hiểu lý do.
Array phù hợp với danh sách có chỉ mục, bắt đầu từ 0. Push thêm cuối và làm thay đổi array; pop lấy ra phần tử cuối, cũng làm thay đổi array. Length là độ dài theo chỉ mục của array, không phải phép đếm mọi thuộc tính có thể gắn vào nó. Với array dày đặc thông thường trong lab, length đúng bằng số mục đang lưu.
Object biểu diễn một mục có các thuộc tính như id, subject, count. Kết hợp array chứa các object rõ hơn việc xen kẽ subject, count trong một array dài hoặc giữ hai array song song dễ lệch nhau. Dùng dấu chấm với tên thuộc tính biết trước và ngoặc vuông khi khóa được chọn động. Không nhận dữ liệu không tin cậy rồi gán tùy tiện vào mọi thuộc tính ứng dụng.
Gán object cho biến khác sao chép giá trị tham chiếu tới cùng object. Spread { ...ticket } tạo object mới ở lớp ngoài nhưng là bản sao nông: object lồng bên trong vẫn có thể dùng chung. Đây là cơ chế chia sẻ, không phải lỗi ngẫu nhiên của browser. Nếu cần bản sửa độc lập ở một nhánh, sao chép cả nhánh thích hợp hoặc chọn công cụ sao chép có ngữ nghĩa phù hợp loại dữ liệu.

Chạy từng đoạn trong module mới hoặc context Console mới để tránh tên const đã khai báo từ lần trước. Không dán mã vào trang có tài khoản thật. Đoạn này chỉ tính trong bộ nhớ và ghi log, không đọc DOM hay gửi mạng.
const raw = " 2 ";
const trimmed = raw.trim();
const count = Number(trimmed);
const valid = trimmed !== "" && Number.isFinite(count)
&& Number.isInteger(count) && count >= 1 && count <= 20;
console.log("COUNT", count, valid);
const tickets = [
{ id: "DEMO-55", subject: "Máy in", count: count,
contact: { channel: "mail" } }
];
const alias = tickets[0];
alias.count += 1;
console.log("SHARED", tickets[0].count);
const shallow = { ...tickets[0] };
shallow.subject = "Bản sao";
shallow.contact.channel = "chat";
console.log("SHALLOW", tickets[0].subject, tickets[0].contact.channel);
const separate = { ...tickets[0], contact: { ...tickets[0].contact } };
separate.contact.channel = "phone";
console.log("SEPARATE", tickets[0].contact.channel, separate.contact.channel);
tickets.push({ id: "DEMO-56", subject: "Màn hình", count: 1 });
const removed = tickets.pop();
console.log("ARRAY", tickets.length, removed.id, Array.isArray(tickets));
let pending;
console.log("EMPTY", pending === undefined, typeof null, Number.isNaN(Number("DEMO")));
Dự đoán kết quả trước: COUNT là 2 và true; SHARED là 3. SHALLOW in subject gốc “Máy in” nhưng channel “chat”, vì subject là thuộc tính lớp ngoài đã sao riêng còn contact vẫn dùng chung. SEPARATE in “chat” và “phone”. ARRAY in 1, DEMO-56 và true. EMPTY in true, “object” và true. Mỗi kết quả kiểm tra một cơ chế khác nhau.
Thay raw lần lượt bằng chuỗi rỗng, khoảng trắng, “2.5”, “DEMO”, “21” và “2”; tải lại module sau mỗi lần. Chỉ trường hợp “2” trong danh sách đó đáp ứng valid. Các dòng phía sau vẫn là minh họa cấu trúc dữ liệu, không phải quy trình production đã chặn mọi dữ liệu sai. Trong ứng dụng thật, cần dừng hoặc trả lỗi trước khi tạo yêu cầu nếu valid là false; bài điều kiện tiếp theo sẽ triển khai ranh giới ấy.
Trong bản sao riêng, thử gán lại const ticket và thử đọc tên let ngoài khối khai báo để nhận biết TypeError/ReferenceError. Không thêm lỗi vào giữa lab rồi mong các log phía sau tiếp tục chạy. Lưu bản gốc trước khi thử để có điểm đối chiếu.
Lab đã được chạy ngày 23/10/2026 bằng Node 22.22.2 và Chromium 151.0.7922.34. Hai runtime cho cùng sáu dòng kết quả của đoạn chính; sáu biến thể raw được kiểm tra riêng. Các lỗi gán lại const, đọc ngoài khối, gán tên chưa khai báo trong strict mode và temporal dead zone được đối chiếu theo loại lỗi. Chưa có biểu mẫu, validation backend hoặc thử mọi browser.
Vì sao const vẫn cho sửa thuộc tính? Khi nào phép nhân chuỗi cho số và khi nào cho NaN? Vì sao Number chuyển được thành 0 chưa đủ chấp nhận đầu vào? Spread có tách contact không? Array.isArray khác typeof thế nào? Hãy giải thích bằng kết quả lab, rồi chuẩn bị dùng hàm và điều kiện để đưa quá trình chuẩn hóa vào một đường xử lý rõ ràng.

Tạo bảng SQLite, hiểu khóa chính và CHECK, dùng CRUD có WHERE và bind tham số từ Node.js; kiểm tra transaction rollback, mở lại dữ liệu và API driver.

Tạo route Express, phục vụ CSS và render EJS; đọc query, params, body, kiểm tra dữ liệu form, escape HTML và phân biệt XSS với CSRF qua lab loopback.

Chạy Node.js LTS, phân biệt ES module và CommonJS, package.json và lockfile; cài package DEMO cục bộ, dùng npm ci và kiểm tra server HTTP trên loopback.

Chọn node DOM, phân biệt collection sống và tĩnh, textContent và innerHTML; cập nhật class, thuộc tính và sự kiện nút bằng chuột, bàn phím trong lab DEMO.