5.2 Khía Cạnh Cấp Cao: Lạc Vào Hố Sâu Bí Ẩn Của JavaScript
Chào mừng bạn đến với Lời bế mạc của Series JavaScript Mastery! Ngôn ngữ này được tạo ra vỏn vẹn trong 10 ngày (bởi Brendan Eich năm 1995), vì thế sự hoàn hảo là điều không thể. JavaScript đã trưởng thành nhưng đồng thời lại mang theo vô số những "di sản" kì quặc về cơ chế mà người ta thường gọi đùa là JS Wat Moments.
Hôm nay, chúng ta sẽ mở bung hoàn toàn hậu trường cuối cùng của JS: Ép kiểu ngầm, Chuỗi kế thừa Prototype, và Bộ ban mượn xác Gọi hàm (Call, Apply, Bind).
🎭 1. Nỗi Điên Đầu Mang Tên Ép Kiểu Ngầm (Type Coercion)
Dưới đây là một trong những cơ chế dễ gây mất ngủ nhất khi mới học JS. JavaScript cực kỳ dễ dãi! Nếu nó thấy 2 kiểu dữ liệu đem ném lên bàn cân tính toán không khớp, rãnh rỗi nó sẽ tự động biến hoá (ép kiểu) một cái cho giống một cái, trước khi báo kết quả cho bạn.
Hãy xem thử đoạn ảo thuật gây tò mò dưới:
- Tại sao? Ở dấu phép toán+, JS ưu tiên "Dán Chuỗi". Nó thấy số 1, bèn ép luôn nó thành chuỗi "1" và dán lại chung với "11". Ngược lại ở dấu -, trong giới văn bản không tồn tại khái niệm "trừ chuỗi", nên máy bắt buộc tính toán số dư. Nó sẽ ép biến "11" trở thành con số kiểu Number, rồi trừ đi 1 bằng 10.
Thậm chí dị hợm hơn: Mảng rỗng mà đi so với nghịch đảo mảng rỗng lại là True?
[!WARNING] Thuốc giải: Không bao giờ dùng
==Ở kỷ nguyên Clean Code hiện tại, LUÔN LUÔN dùng Strict Equality===hoặc!==(Chặt chẽ: so sánh trọn gói giá trị cộng cả kiểu Type). Dùng hệ quy chiếu khắt khe này, chuỗi"11"sẽ không bao giờ có cửa ngang hàng với số11nữa.
🧬 2. Thế Lực Cổ Đại: Kế Thừa Căn Nguyên (Prototypal Inheritance)
Nếu bạn từng học Java hay C#, bạn sẽ quen với khái niệm Class (Nhà tạo mẫu) sinh ra Object con bằng Class-based inheritance. JS thì khác biệt từ gốc! Nó xài Prototype-based Inheritance (Kế thừa theo Chuỗi Phả Hệ Nguyên Mẫu). Tức là các sự vật nhận sự chia sẻ tính năng bằng cách... xích một đường dẫn liên kết ngược về Tổ Tiên của nó!
Nếu bạn lệnh cho 1 đối tượng rỗng con tự làm 1 hành động mà nó không có, tự động nó "bỏ chạy lên" nhờ cái Object Tổ hỏi có hàm đó không mượn lấy để chạy đỡ.
// Chế tạo Cụ Tổ Tiên (Prototype)
const HieuTruongPrototype = {
sayGreeting: function() {
console.log("Chào mừng các em tới trường! Tôi đứng ở: " + this.name);
}
};
// JS Hỗ trợ móc dây phả hệ với Object.create()
const hocSinh = Object.create(HieuTruongPrototype);
hocSinh.name = "Nobita";
// Dù hocSinh không tự biên dịch hàm sayGreeting bên trong nó,
// nhưng nhờ dây xích truyền thừa, nó ngược lên mượn hàm ông Tổ để nói!
hocSinh.sayGreeting(); // In ra: Chào mừng các em tới trường! Tôi đứng ở: Nobita
Sự thật động trời là: Cái class mà chúng ta viết tung tẩy hiện tại trong Angular/React (cú pháp ES6 class Animal { }), bản chất dưới tầng mã máy (Under the hood) nó CHỈ LÀ CÚ LỪA ĐƯỜNG (Syntactic Sugar) mà thôi, bên dưới rễ nó vẫn bị biên dịch ngược về y bài cơ chế Mắc Xích Nguyên Mẫu ngầm này.
🎎 3. Bộ 3 Phép Thuật: Trói Hồn Đoạt Mệnh Bind - Call - Apply
Ở bài 2.2, ta đã hiểu biến this trỏ về đâu tuỳ thuộc vào Kẻ Nào Đang Thực Hiện Hàm.
Thế nếu một Object A muốn "mượn tạm xác" một Hàm riêng bên object B về dùng, mà muốn Hàm đó nhận diện B là this của chính A thì sao?
JS cung cấp bộ ba chức trách để Mượn hàm và bắt Ép con trỏ this.
Kẻ Cưỡng Chế 1 & 2: .call() và .apply()
Hai lệnh này có tác dụng: Mượn một Hàm, Trói this vào một Object chỉ định, và THỰC THI CHẠY NGAY TẠP TỨC.
Khác biệt nhỏ: Call truyền từng biến rời lẻ loi, Apply ăn tất cả ngọn gàng vô mảng thẻ cào.
const person1 = { firstName: "Sơ", lastName: "Saitama" };
const person2 = { firstName: "Sư", lastName: "Goku" };
function getFullName(giay_Gio_Thi, giay_Khai_Sinh) {
return giay_Gio_Thi + " " + this.firstName + " " + this.lastName + " - " + giay_Khai_Sinh;
}
// 💥 Call - Truyền phẩy từng tham số cực rạch ròi!
console.log(getFullName.call(person1, "Anh Hói", "Hội Anh Hùng"));
// In ra: "Anh Hói Sơ Saitama - Hội Anh Hùng"
// 💥 Apply - Cầm một cụm nguyên rổ Array [] nhét vào! Bổ ích nếu data của bạn là 1 mảng cho sẵn.
console.log(getFullName.apply(person2, ["Anh Khỉ", "Phi Đội Saiyan"]));
// In ra: "Anh Khỉ Sư Goku - Phi Đội Saiyan"
Kẻ Bất Diệt Xuyên Không 3: .bind()
Hiệu năng cao hơn cực kì cho quá trình Bất Đồng Bộ. Khác 2 ông ở trên, bind() ĐÓNG BĂNG HÀM LẠI, chốt chặn linh hồn của this nhét thẳng vô nó, trả bạn về 1 cục bản sao vĩnh cửu, và KHÔNG CHẠY NGAY! - Chỉ đợi tới tương lai khi nào bạn cần thì bật nó nhả ra. Rất hay đi cặp với setTimeout hoặc nhét vô EventListener UI.
const user = { name: "Batman" };
function sayHello() {
console.log("Hey, I'm " + this.name);
}
// Chốt cái mớ xác này vĩnh viễn trỏ vào object Batman
const lockedHello = sayHello.bind(user);
// Thong dong 5 Giây sau khởi chạy! Hàm this không hề trát lạc ra window dẫu bị Event Loop ném đi.
setTimeout(lockedHello, 5000);
🏆 KẾT LUẬN TUYỆT HẢO
Vậy là chúng ta đã khám phá tới những hạt nhân góc khuất nhất ở đáy hốc cấu thành của Javascript. Nếu bạn đã nắm vũng: - Kỷ nguyên Biến & Bộ Nhớ (Var/Let, Const, Stack vs Heap, V8 Engine). - Trình tự thực chiến Code Dọn Dẹp (Hoisting, Phân tích Scope, Closure mảng nắp đóng Môt). - Khơi mào cuộc phản công tốc độ Web DOM Warfare. - Tiến hóa xuyên suốt Async API tới Kỉ nguyên Băng Tần Reactive NgRx (RxJS/Map). - Và hạ chốt các cấu trúc Nguyên Mẫu Kế thừa Lõi (Prototype Binding).
Thì chúc mừng bạn! Bạn không chỉ học cách Code với Framework để dựng màn hình gõ thô. Bạn đã đủ hành trang để đọc và hiểu bản chất mọi nền tảng sinh thái viết bằng ngôn ngữ Javascript/Typescript toàn diện nhất, vững vàng và trở thành một Senior JS Warror thực thụ! 🚀