TypeScript là gì? TypeScript là ngôn ngữ lập trình được yêu thích thứ 5 trong năm 2023 và đang trở nên phổ biến trong nhiều framework hiện đại. Ngay cả khi bạn chưa có nhu cầu sử dụng, việc học TypeScript cũng giúp bạn hiểu sâu hơn về JavaScript và mở rộng cơ hội có việc làm tốt hơn trong tương lai!
Typescript là gì?
Nhìn lại lịch sử, JavaScript là ngôn ngữ chính để viết web vì nó có thể sử dụng được trên cả front-end lẫn back-end của các framework như Node.js và Deno. Nhưng khi các trang web phát triển thành một hệ thống lớn và phức tạp hơn, thì sử dụng JavaScript sẽ gặp khó khăn trong việc đọc code và bảo trì.
Microsoft đã sớm nhận thấy vấn đề này và giải pháp của họ là TypeScript! Từ phiên bản đầu tiên được phát hành năm 2012, TypeScript ngày càng trở nên phổ biến và càng có nhiều công việc đòi hỏi các developer phải biết TypeScript.
Theo báo cáo Developer Survey 2023 của Stackoverflow, TypeScript là ngôn ngữ phổ biến thứ 5 đối với các developer chuyên nghiệp. TypeScript cũng đứng thứ 3 trong danh sách “những ngôn ngữ mong muốn sử dụng” đối với developer.
Do đó, học hỏi và trau dồi kiến thức về TypeScript sẽ không phí thời gian của bạn, đặc biệt nếu bạn là web developer đang tìm kiếm những cơ hội việc làm hoặc cơ hội thăng tiến tốt hơn.
Hãy sử dụng 20 câu hỏi đáp về TypeScript dưới đây để sẵn sàng cho buổi nói chuyện sắp tới của bạn với nhà tuyển dụng hoặc Tech Leader!
Đọc full bài 20 câu tiếng Anh tại đây: https://arc.dev/developer-blog/typescript-interview-questions/
Các câu hỏi về TypeScript trong sẽ được chia thành ba nhóm:
- Câu hỏi phỏng vấn TypeScript sơ cấp
- Câu hỏi phỏng vấn TypeScript trung cấp
- Câu hỏi phỏng vấn TypeScript nâng cao
Những gợi ý trả lời sẽ không chỉ về mặt kỹ thuật, mà còn về lý do đằng sau câu hỏi: Tại sao người phỏng vấn lại hỏi điều này? Họ thực sự muốn biết điều gì? Làm thế nào để câu trả lời của bạn đáp ứng tốt nhất?
Trong phần 1 này, chúng ta sẽ đi qua nhóm câu hỏi phỏng vấn TypeScript sơ cấp.
Bộ câu hỏi sơ cấp sẽ kiểm tra kiến thức cơ bản về TypeScript và một số tính năng cốt lõi của nó.
TypeScript là gì và nó khác với JavaScript như thế nào?
Tips: Đây là câu hỏi nền tảng nhằm đánh giá mức độ quen thuộc với ngôn ngữ và các tính năng liên quan. Bạn cần đảm bảo mô tả được TypeScript trong khái niệm của một ngôn ngữ lập trình, đồng thời nêu được các tính năng cốt lõi và mối liên hệ của nó với JavaScript.
Trả lời:
TypeScript là một superset (tập mẫu) của JavaScript, có thể biên dịch sang JavaScript thuần. Về mặt khái niệm, mối quan hệ giữa TypeScript và JavaScript có thể so sánh với mối quan hệ của SASS và CSS.
Nói cách khác, TypeScript là phiên bản ES6 của JavaScript với một số tính năng bổ sung.
TypeScript là một ngôn ngữ nhập tĩnh (statical typed) và hướng đối tượng (object oriented), tương tự như Java và C#. Trong khi đó, JavaScript là một ngôn ngữ kịch bản (scripting language) gần giống Python. Bản chất hướng đối tượng của TypeScript trở nên hoàn thiện với các tính năng như class và interface. Tính năng nhập tĩnh của TypeScript cho phép hiệu chỉnh tốt hơn thông qua việc suy luận kiểu (type inference) tuỳ theo ý của bạn.
Về mặt code, TypeScript được viết trong tệp có đuôi .ts, trong khi JavaScript có đuôi .js. Không giống như JavaScript, trình duyệt sẽ không thể hiểu được code trong TypeScript, loại code này cũng không thể thực thi trực tiếp trong trình duyệt hoặc bất kỳ nền tảng nào khác.
Do đó, trước tiên, các tệp .ts cần phải được biên dịch sang JavaScript thuần, thông qua trình biên dịch tsc của TypeScript. Sau đó, chúng sẽ được thực thi bởi nền tảng đích.
Lợi ích của việc sử dụng TypeScript là gì?
Tips: Mỗi ngôn ngữ lập trình đều có ưu – nhược điểm và lý do chúng được sử dụng. Câu hỏi này nhằm để hiểu rõ hơn về trải nghiệm cá nhân của dev khi sử dụng TypeScript, cũng như cách nó ảnh hưởng đến quy trình làm việc của dự án. Do đó, thay vì chỉ liệt kê các lợi ích của TypeScript, hãy dẫn chứng rằng một dự án điển hình có thể hưởng lợi gì từ nó, ví dụ như khả năng bảo trì tốt hơn trong thời gian dài, hoặc giúp developer tăng năng suất làm việc.
Trả lời:
Một lợi thế dễ thấy của TypeScript là tooling. TypeScript là một ngôn ngữ strong typing (nghĩa là dạng của đối tượng được giữ nguyên trừ khi có lệnh rõ ràng yêu cầu thay đổi) và sử dụng type inference (suy luận kiểu). Những đặc điểm này giúp tooling tốt hơn và tích hợp chặt chẽ hơn với các trình sửa code. Sự kiểm tra nghiêm ngặt của TypeScript giúp phát hiện sớm các lỗi, giảm đáng kể khả năng mắc lỗi chính tả và các lỗi khác do con người gây ra.
Từ góc độ của IDE (Integrated development environment – Môi trường phát triển tích hợp), TypeScript giúp IDE hiểu code tốt hơn bằng cách cho phép nó hiển thị gợi ý, cảnh báo và lỗi sai rõ ràng hơn đến developer.
- Ví dụ: TypeScript kiểm tra null và báo lỗi ngay tại thời điểm biên dịch (và trong IDE của bạn), nhờ đó ngăn chặn một lỗi phổ biến trong JavaScript đó là truy cập vào thuộc tính của một biến không xác định trong thời gian chạy.
Lợi ích lâu dài của việc sử dụng TypeScript là khả năng mở rộng và khả năng bảo trì. Khả năng mô tả cụ thể các đối tượng và hàm trực tiếp trong code giúp cho codebase của bạn trở nên dễ hiểu hơn, dễ dự đoán hơn. Khi được sử dụng đúng cách, TypeScript cung cấp một ngôn ngữ chuẩn hóa giúp developer đọc code tốt hơn, từ đó có thể tiết kiệm thời gian và công sức khi codebase tiếp tục phát triển.
Interface trong TypeScript là gì?
Tips: Tương tự câu hỏi về tính năng, câu hỏi này kiểm tra độ hiểu biết và quen thuộc của ứng viên đối với ngôn ngữ. Một câu trả lời lý tưởng cần bao gồm cách sử dụng và lợi ích của interface trong TypeScript.
Interface là cách TypeScript xác định cú pháp của các thực thể. Nói cách khác, interface là một cách để mô tả các data shape như objects (đối tượng) hoặc array of objects (mảng đối tượng).
Bạn có thể thiết lập interface bằng từ khóa interface, tiếp đến là tên và định nghĩa. Cùng xem cách thiết lập một interface đơn giản cho object “User”:
interface User { name: string; age: number; } |
Sau đó, interface này có thể dùng để đặt kiểu cho một biến (tương tự như cách bạn gán kiểu nguyên thủy – primitive type cho biến). Khi đó, một biến có type “User” sẽ tuân theo các thuộc tính của interface.
let user: User = { name: “Bob”, age: 20, // omitting the `age` property or a assigning a different type instead of a number would throw an error }; |
Các interface giúp thúc đẩy tính nhất quán trong dự án TypeScript. Thêm nữa, các interface cũng giúp cải thiện tooling của dự án, cung cấp chức năng autocomplete trong IDE tốt hơn và đảm bảo các giá trị đúng đang được truyền vào constructor và hàm.
Làm thế nào để tạo type mới bằng một tập hợp con của interface?
Tips: Việc sửa đổi interface rất có lợi để loại bỏ code trùng lặp và tối đa hoá khả năng tái sử dụng của các interface hiện có (nếu điều đó hợp lý). Câu hỏi này nhắc đến một trong nhiều tính năng mà TypeScript cung cấp để tạo ra một interface mới từ interface hiện có.
TypeScript có một utility type gọi là “omit” (bỏ qua), cho phép bạn tạo ra một type mới bằng cách pass một type/ interface đang có và chọn lựa các key sẽ được loại bỏ khỏi type mới.
Ví dụ dưới đây cho thấy cách tạo ra type mới “UserPreview” dựa trên interface “User” đã có, nhưng đã được loại bỏ thuộc tính email:
interface User { name: string; description: string; age: number; email: string; } // removes the `email` property from the User interface type UserPreview = Omit<User, “email”>; const userPreview: UserPreview = { name: “Bob”, description: “Awesome guy”, age: 20, }; |
Các “enum” hoạt động như thế nào trong TypeScript?
Tips: Enum là một cấu trúc dữ liệu phổ biến trong hầu hết các ngôn ngữ typed. Hiểu rõ về enum và cách sử dụng chúng là một phần quan trọng trong việc tổ chức code và giúp code trở nên dễ đọc hơn. Bạn cần giải thích về enum ở cấp độ cao, đồng thời trình bày được các tính năng và cách sử dụng cơ bản của chúng.
Enum – hay còn gọi là enumerated types (kiểu liệt kê) là một phương tiện để xác định một tập hợp các hằng số được đặt tên. Các cấu trúc dữ liệu này có độ dài không đổi và chứa một tập hợp các giá trị không đổi. Enum trong TypeScript thường được dùng để biểu diễn một số lượng nhất định các tùy chọn cho một giá trị cho trước, thông qua một tập hợp các cặp key/ value.
Hãy xem ví dụ về enum dùng để xác định một tập hợp các “user type”
enum UserType { Guest = “G”, Verified = “V”, Admin = “A”, } const userType: UserType = UserType.Verified; |
Ở bên dưới, TypeScript sẽ biên dịch các enum thành các đối tượng JavaScript thuần. Điều này khiến việc sử dụng enum có lợi hơn so với sử dụng nhiều biến const độc lập. Nhóm mà enum tạo ra giúp cho code của bạn an toàn về type và dễ đọc hơn.
Hàm arrow (hàm mũi tên) trong TypeScript là gì?
Tips: Arrow function là một tính năng phổ biến của ES6 và TypeScript nhằm giới thiệu cách khác ngắn hơn để xác định các hàm. Arrow function cũng có những ưu và nhược điểm quan trọng mà bạn cần xem xét khi lựa chọn phương pháp nào sẽ được sử dụng.
Hàm arrow – hay còn được gọi là hàm lambda, cung cấp một cú pháp ngắn gọn và thuận tiện để khai báo các hàm. Các hàm arrow thường được sử dụng để tạo các hàm gọi lại (callback function) trong TypeScript. Các phép toán mảng (array operations) như map, filter, và reduce đều chấp nhận các hàm arrow làm đối số của chúng.
Tuy nhiên, tính ẩn danh của hàm arrow cũng có mặt trái. Nếu dùng không đúng, cú pháp ngắn hơn có thể gây khó hiểu hơn. Hơn nữa, bản chất không tên của các hàm arrow cũng khiến nó không thể tạo các hàm tự tham chiếu (tức là đệ quy).
Chúng ta hãy xem cách một hàm chính quy (regular function) chấp nhận hai số và trả về tổng của nó.
function addNumbers(x: number, y: number): number { return x + y; } addNumbers(1, 2); // returns 3 |
Bây giờ, hãy chuyển đổi hàm trên thành một hàm arrow:
const addNumbers = (x: number, y: number): number => { return x + y; }; addNumbers(1, 2); // returns 3 |
Sự khác nhau giữa var, let và const trong TypeScript là gì?
Tips: TypeScript cung cấp ba cách khai báo biến khác nhau: var, let và const. Phân biệt được ba từ khóa này, hiểu được khi nào nên dùng từ nào là yêu cầu quan trọng để viết code chất lượng. Hãy đảm bảo rằng bạn có trình bày về các trường hợp sử dụng cụ thể đối với từng từ khóa, kèm theo tính chất của chúng.
- var: Khai báo biến function scope hoặc biến toàn cục (global scope), có tính chất và quy tắc phạm vi tương tự với các biến var của JavaScript. Các biến var không yêu cầu gán giá trị cho nó trong quá trình khai báo.
- let: Khai báo một biến cục bộ trong phạm vi khối (block-scoped local variable). Các biến let không yêu cầu gán giá trị cho một biến trong quá trình khai báo. Block-scoped local variable có nghĩa là biến chỉ có thể được truy cập trong khối chứa của nó, chẳng hạn như một hàm, một khối if/ else hoặc một vòng lặp. Hơn nữa, không giống như var, chúng ta không thể đọc hoặc ghi biến let trước khi chúng được khai báo.
// reading/writing before a `let` variable is declared console.log(“age”, age); // Compiler Error: error TS2448: Block-scoped variable ‘age’ used before its declaration age = 20; // Compiler Error: error TS2448: Block-scoped variable ‘age’ used before its declaration let age: number; // accessing `let` variable outside it’s scope function user(): void { let name: string; if (true) { let email: string; console.log(name); // OK console.log(email); // OK } console.log(name); // OK console.log(email); // Compiler Error: Cannot find name ’email’ } |
- const: Khai báo một giá trị hằng số trong phạm vi khối mà giá trị đó không thể thay đổi sau khi khởi tạo. Các biến const yêu cầu việc khởi tạo như một phần trong quá trình khai báo. Điều này trở nên lý tưởng đối với các biến không thay đổi trong suốt thời gian tồn tại của chúng.
// reassigning a `const` variable const age: number = 20; age = 30; // Compiler Error: Cannot assign to ‘age’ because it is a constant or read-only property // declaring a `const` variable without initialization const name: string; // Compiler Error: const declaration must be initialized |
Khi nào bạn sử dụng return type là “never” và nó khác với “void” như thế nào?
Tips: Đây là câu hỏi thể hiện hiểu biết của bạn về các “type” trong TypeScript và cách sử dụng chúng trong việc xác định return type. Bạn cần phân biệt giữa hai type và xác định được return type của một hàm thông qua việc xem nội dung của nó.
Trước khi đi sâu vào sự khác biệt giữa never và void, cùng nói về hoạt động của một hàm JavaScript khi không có gì được trả về rõ ràng.
Hãy xem qua hàm trong ví dụ bên dưới. Nó không trả lại bất cứ điều gì rõ ràng cho caller (hàm gọi). Tuy nhiên, nếu bạn gán nó cho một biến và ghi lại giá trị của biến đó, bạn sẽ thấy rằng giá trị của hàm là undefined (không xác định).
printName(name: string): void { console.log(name); } const printer = printName(‘Will’); console.log(printer); // logs “undefined” |
Đoạn mã trên là một ví dụ về void. Các hàm không trả về rõ ràng được TypeScript suy luận có return type là void.
Ngược lại, never là hàm đại diện cho một giá trị không bao giờ xảy ra. Ví dụ, một hàm có vòng lặp vô hạn hoặc một hàm báo lỗi là những hàm có return type là never.
const error = (): never => { throw new Error(“”); }; |
Tóm lại, void được sử dụng khi một hàm không trả về bất kỳ thứ gì rõ ràng, còn never được sử dụng khi một hàm không bao giờ trả về.
TypeScript hỗ trợ những Access modifier (phạm vi truy cập) nào?
Tips: Access modifier (phạm vi truy cập) và encapsulation (sự đóng gói) song hành với nhau. Câu hỏi này nhằm để hiểu kiến thức của ứng viên về khái niệm “đóng gói”, cũng như về cách tiếp cận của TypeScript với việc hạn chế truy cập dữ liệu. Người hỏi cũng có thể đang muốn nghe về các ứng dụng thực tế của phạm vi truy cập, lý do đằng sau và lợi ích của chúng
Khái niệm “encapsulation” (đóng gói) được sử dụng trong lập trình hướng đối tượng, nhằm kiểm soát khả năng hiển thị những thuộc tính và phương thức của các đối tượng. TypeScript sử dụng các access modifier để cài đặt khả năng hiển thị nội dung của một class (lớp). Vì TypeScript được biên dịch sang JavaScript, logic liên quan đến phạm vi truy cập được áp dụng trong thời gian biên dịch, không phải trong lúc chạy
Có ba loại access modifier trong TypeScript là: public, private, và protected.
- public: Tất cả các thuộc tính và phương thức được mặc định công khai. Chúng ta có thể nhìn thấy và truy cập các member công khai của một class từ bất kỳ vị trí nào.
- protected: Các thuộc tính được bảo vệ có thể truy cập được từ trong cùng một class và subclass của nó.
Ví dụ: một biến hoặc phương thức với từ khóa protected sẽ có thể truy cập được từ bất kỳ đâu trong class của nó, đồng thời trong một class khác mở rộng class chứa biến hoặc phương thức đó.
- private: Các thuộc tính riêng tư chỉ có thể truy cập được từ trong class mà thuộc tính hoặc phương thức đó được định nghĩa.
Để sử dụng bất kỳ phạm vi truy cập nào trong số trên, hãy thêm từ khoá public, private, và protected đằng trước thuộc tính hoặc phương thức (nếu bị bỏ qua, TypeScript sẽ mặc định là public)
class User { private username; // only accessible inside the `User` class // only accessible inside the `User` class and its subclass protected updateUser(): void {} // accessible from any location public getUser() {} } |
Vi phạm các quy tắc của phạm vi truy cập, ví dụ như cố gắng truy cập thuộc tính private của một class từ một class khác sẽ dẫn đến lỗi dưới đây trong quá trình biên dịch
Property ‘<property-name>’ is private and only accessible within class ‘<class-name>’. |
Tóm lại, phạm vi truy cập đóng một vai trò quan trọng trong việc sắp xếp code. Chúng cho phép hiển thị một tập hợp các API công khai và ẩn đi các chi tiết triển khai. Bạn có thể xem phạm vi truy cập như những cánh cổng dẫn vào class. Sử dụng hiệu quả các phạm vi truy cập sẽ làm giảm đáng kể khả năng xảy ra lỗi do sử dụng nhầm nội dung của class khác.
Generic là gì và cách sử dụng chúng trong TypeScript?
Tips: Bên cạnh ý “là gì”, người hỏi cũng có thể đang muốn biết các hàm generic liên quan đến tính linh hoạt của code thế nào, cũng như lý do đằng sau việc sử dụng generic thay vì các type khác. Hãy chia sẻ hiểu biết về generic là gì, cách hoạt động và lợi ích của nó.
Những bài học “đắt giá” về kỹ thuật phần mềm thường khuyến khích khả năng tái sử dụng và tính linh hoạt. Việc sử dụng generic mang đến khả năng tái sử dụng và tính linh hoạt bằng cách: Cho phép một component hoạt động trên nhiều type thay vì một type duy nhất, mà vẫn giữ được độ chính xác (không giống như việc sử dụng hàm any).
Dưới đây là một ví dụ về một hàm generic cho phép caller xác định type sẽ được sử dụng trong hàm.
function updateUser<Type>(arg: Type): Type { return arg; } |
Để gọi một hàm generic, bạn có thể truyền trực tiếp trong type thông qua dấu ngoặc nhọn <> hoặc thông qua type argument inference (cho phép TypeScript suy ra type dựa trên kiểu của đối số được truyền vào).
// explicitly specifying the type let user = updateUser<string>(“Bob”); // type argument inference let user = updateUser(“Bob”); |
Generic cho phép chúng ta theo dõi thông tin type trong hàm. Điều này làm cho code trở nên linh hoạt và có thể tái sử dụng mà không ảnh hưởng đến độ chính xác của loại code.
Tổng kết
Trên đây là những câu hỏi cơ bản về TypeScript mà bạn có thể gặp trong các buổi technical interview. Hãy nắm chắc những kiến thức nền tảng này, sau đó tiếp tục ôn luyện với những câu hỏi trung cấp và cao cấp ở phần 2 của loạt bài về ‘Typescript là gì” của ITviec nhé.
Bạn thấy bài viết hay và cần thiết với nhiều người? Đừng ngại nhấn nút Share bên dưới nhé.
Và đừng quên tham khảo việc làm IT trên ITviec!