Menu

TypeScript và JavaScript: khác nhau thế nào, có ví dụ

TypeScript là JavaScript cộng thêm một hệ thống kiểu tĩnh được kiểm tra trước khi code chạy. So sánh hai ngôn ngữ song song: cú pháp, những lỗi bộ kiểm tra kiểu bắt được, bước build, tốc độ khi chạy, độ khó khi học và cách chuyển một dự án JavaScript sang TypeScript.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

TypeScript là JavaScript có thêm một hệ thống kiểu tĩnh (static type system) đặt lên trên. Mọi chương trình JavaScript đều là cú pháp TypeScript hợp lệ; TypeScript bổ sung chú thích kiểu, một trình biên dịch kiểm tra chúng trước khi code chạy, và một bước build xóa chúng đi. Lúc chạy chỉ còn JavaScript, nên khác biệt nằm hoàn toàn ở những gì bạn phát hiện được trước khi phát hành.

Trong JavaScript, cùng hàm này chính là đoạn code trên bỏ đi type Product = ..., : Product[] và : number. Các kiểu bổ sung thông tin cho trình biên dịch và editor; chúng không thay đổi việc chương trình làm.

So sánh nhanh TypeScript và JavaScript

JavaScriptTypeScript
Hệ thống kiểuĐộng: kiểu thuộc về giá trị và chỉ được biết lúc chạyTĩnh: kiểu được khai báo hoặc suy luận và kiểm tra lúc biên dịch
Khi nào lỗi kiểu xuất hiệnKhi dòng đó chạy (undefined, NaN, TypeError)Ngay trong editor khi bạn gõ, và khi biên dịch
Chạy ở đâuTrình duyệt, Node.js, Deno, Bun, chạy trực tiếpCùng những nơi đó, sau khi các kiểu bị xóa
Bước buildKhông cầntsc hoặc một bundler, hoặc runtime tự loại bỏ kiểu
Tệp.js, .mjs, .cjs.ts, .mts, .cts, .tsx, cùng tệp khai báo kiểu .d.ts
Tốc độ khi chạyMức cơ sởGiống hệt: đầu ra là JavaScript
Hỗ trợ editorGợi ý code từ kiểu suy luận và typings của thư viện, có thể không đầy đủGợi ý code, đổi tên và "find all references" dựa trên kiểu đã khai báo
Độ khó khi họcThấp hơnJavaScript cộng thêm hệ thống kiểu
ChuẩnECMAScript, do TC39 phát triểnDự án mã nguồn mở của Microsoft, bám theo ECMAScript

Cùng một đoạn code ở hai ngôn ngữ

Đây là một hàm viết bằng JavaScript. Không có gì trong đó cho biết user phải có dạng như thế nào:

function greeting(user) {
    return `Hello, ${user.firstName} ${user.lastName}`;
}

greeting({ firstname: "Ada", lastName: "Lovelace" });
// "Hello, undefined Lovelace", no error anywhere

Phiên bản TypeScript khai báo hình dạng một lần, và lỗi gõ sai được báo trước khi code chạy:

interface User {
    firstName: string;
    lastName: string;
}

function greeting(user: User): string {
    return `Hello, ${user.firstName} ${user.lastName}`;
}

greeting({ firstname: "Ada", lastName: "Lovelace" });
// error TS2561: Object literal may only specify known properties,
// but 'firstname' does not exist in type 'User'. Did you mean to write 'firstName'?

Chú thích kiểu là toàn bộ khác biệt về cú pháp. TypeScript cũng thêm vài kiểu khai báo riêng (interface, type, enum, generic như Array<string>, access modifier như private), nhưng các câu lệnh, toán tử và đối tượng có sẵn đều là của JavaScript.

Những lỗi TypeScript bắt được mà JavaScript thì không

JavaScript chuyển kiểu một cách lặng lẽ. Bug này rất hay gặp với giá trị lấy từ các ô nhập liệu của form, vốn luôn là chuỗi. Chạy thử để xem trình biên dịch nói gì:

index.ts(7,17): error TS2345: Argument of type 'string[]' is not assignable to parameter of type 'number[]'.
  Type 'string' is not assignable to type 'number'.

Trong JavaScript, đoạn này chạy được và in ra 010205, vì 0 + "10" là phép nối chuỗi. TypeScript từ chối biên dịch cho đến khi các chuỗi được chuyển đổi, ví dụ bằng fromForm.map(Number).

Nhóm lỗi lớn còn lại là những giá trị có thể không tồn tại. Array.prototype.find trả về undefined khi không tìm thấy phần tử nào khớp, và TypeScript buộc bạn phải xử lý trường hợp đó:

index.ts(8,13): error TS18048: 'user' is possibly 'undefined'.

Phiên bản JavaScript thuần bị crash lúc chạy với TypeError: Cannot read properties of undefined (reading 'name'). Cách sửa trong TypeScript là xử lý đúng trường hợp mà trình biên dịch đã chỉ ra:

Đầu ra:

GRACE
no user with id 3

Những gì TypeScript không bắt được: lỗi logic (một công thức sai vẫn có kiểu đúng), và mọi thứ liên quan đến dữ liệu đi vào chương trình lúc chạy. Một response API có kiểu User chỉ đúng khi máy chủ gửi nó đúng, vì các kiểu đã biến mất khi code chạy. Hãy kiểm tra những dữ liệu như vậy bằng code chạy lúc runtime.

Dùng thư viện JavaScript trong TypeScript

Mọi package npm đều dùng được từ TypeScript, vì đầu ra dù sao cũng là JavaScript. Kiểu cho một package đến từ một trong ba nơi:

  • Package tự kèm tệp .d.ts. Hầu hết package còn được duy trì tích cực đều làm vậy, và bạn không cần cài gì thêm.
  • Một package @types riêng từ dự án cộng đồng DefinitelyTyped: npm install --save-dev @types/lodash thêm kiểu cho lodash.
  • Không có ở đâu cả. Khi đó, nếu bật strict, chính câu lệnh import sẽ bị báo lỗi:
error TS7016: Could not find a declaration file for module 'lodash'. '/project/node_modules/lodash/lodash.js' implicitly has an 'any' type.
  Try `npm i --save-dev @types/lodash` if it exists or add a new declaration (.d.ts) file containing `declare module 'lodash';`

Cách sửa là cài package @types nếu có, hoặc tự mô tả module trong một tệp .d.ts; trang declaration file hướng dẫn cách làm.

Bước build

Trình duyệt và Node.js không kiểm tra kiểu, nên TypeScript cần một bước nằm giữa mã nguồn và code thực sự chạy. Có ba cách thiết lập phổ biến:

  • tsc biên dịch mọi thứ. Nó kiểm tra kiểu và ghi ra các tệp .js, thường vào thư mục dist. Đơn giản, và là chuẩn cho các thư viện.
  • Bundler hoặc dev server xóa kiểu, còn tsc --noEmit kiểm tra chúng. Vite và esbuild loại bỏ kiểu mà không kiểm tra, giúp tải lại nhanh; editor và một bước CI chạy bộ kiểm tra kiểu.
  • Runtime tự xóa kiểu. Các phiên bản Node.js hiện tại, Deno và Bun chạy trực tiếp tệp .ts. Không runtime nào kiểm tra kiểu khi chạy, nên tsc --noEmit (hoặc deno check) vẫn là cách để tìm lỗi kiểu.

JavaScript không cần bất cứ bước nào trong số này, và đó là lợi thế thực tế lớn nhất của nó với các script nhỏ. Chi phí của bước TypeScript chủ yếu là thiết lập, gồm một tsconfig.json và dev dependency typescript, cộng với thời gian biên dịch; trình biên dịch native của TypeScript 7 đã giảm thời gian đó khoảng mười lần trên các dự án lớn.

Độ khó khi học

Mọi điều bạn biết về JavaScript đều dùng được, vì phần chạy của TypeScript chính là JavaScript. Phần mới là hệ thống kiểu, và nó gồm nhiều lớp:

  1. Chú thích kiểu cho biến, tham số và giá trị trả về (: string, : number[]).
  2. Kiểu đối tượng với interface và type, thuộc tính tùy chọn, union như string | number.
  3. Thu hẹp kiểu (narrowing): kiểm tra một giá trị bằng typeof, in hoặc === để trình biên dịch biết bạn đang ở trường hợp nào.
  4. Generic, utility type như Partial<T> và Pick<T, K>, và các kiểu nâng cao dành cho người viết thư viện.

Hai lớp đầu tiên đủ cho phần lớn code ứng dụng. Phần lớn kiểu được suy luận tự động, nên nhiều code TypeScript trông giống JavaScript, chỉ có chú thích kiểu ở chữ ký hàm.

Khi nào chọn TypeScript, khi nào chọn JavaScript

TypeScript có tốt hơn JavaScript không? Với code do nhiều người duy trì hoặc tồn tại nhiều năm, thường là có, và ngành phần mềm đã đi theo hướng đó: theo số người đóng góp hằng tháng mà GitHub thống kê, TypeScript đã vượt cả JavaScript và Python vào tháng 8 năm 2025 để trở thành ngôn ngữ được dùng nhiều nhất trên GitHub. Với các script nhỏ, JavaScript thuần thường là công cụ phù hợp hơn.

Chọn TypeScript khi:

  • Có nhiều hơn một người làm việc trên code, hoặc code sẽ được duy trì trong nhiều tháng, nhiều năm.
  • Codebase đủ lớn để bạn không thể nhớ hết chữ ký của mọi hàm.
  • Bạn thường xuyên refactor: đổi tên một thuộc tính sẽ cập nhật mọi chỗ dùng, và trình biên dịch liệt kê những chỗ còn sót.
  • Bạn phát hành một thư viện: các tệp .d.ts mang lại gợi ý code và kiểm tra cho người dùng thư viện.
  • Framework yêu cầu nó. Ứng dụng Angular được viết bằng TypeScript, Next.js và Astro mặc định tạo dự án mới bằng TypeScript, và các template React, Vue, Svelte của Vite đều có bản TypeScript.

Chọn JavaScript khi:

  • Chương trình là một script ngắn, một thử nghiệm dùng một lần, hoặc một đoạn code trong console của trình duyệt.
  • Bạn mới học lập trình lần đầu và muốn tiếp cận ít khái niệm hơn cùng lúc.
  • Không có bước build và bạn cũng không muốn có. Kể cả khi đó, // @ts-check kết hợp JSDoc vẫn cho bạn một phần kiểm tra trong tệp .js thông thường.

Chuyển dự án JavaScript sang TypeScript

Việc chuyển đổi không cần diễn ra một lần. Trình biên dịch chấp nhận JavaScript bên cạnh TypeScript:

{
    "compilerOptions": {
        "allowJs": true,
        "checkJs": false,
        "outDir": "dist",
        "rootDir": "src"
    },
    "include": ["src"]
}

Với allowJs, các tệp .js được biên dịch và có thể import từ tệp .ts, cũng như ngược lại. Sau đó chuyển dần từng bước:

  1. Đổi tên một tệp từ .js thành .ts và sửa các lỗi trình biên dịch báo trong tệp đó.
  2. Bắt đầu từ các tệp lá (module tiện ích ít import), rồi tiến dần vào trong.
  3. Bật checkJs, hoặc thêm // @ts-check ở đầu từng tệp .js, để kiểm tra kiểu các tệp chưa đổi tên.

Trong các tệp JavaScript được kiểm tra, chú thích JSDoc cung cấp kiểu:

// @ts-check

/**
 * @param {number} price
 * @param {number} qty
 * @returns {number}
 */
function lineTotal(price, qty) {
    return price * qty;
}

lineTotal("3", 2);
// error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

Một số nhóm dừng ở đó: tệp JavaScript có kiểu JSDoc, được tsc kiểm tra, và không có bước build cho chính code. Số khác chuyển hẳn sang .ts. Nếu bạn bật strict cho một dự án có sẵn, hãy chuẩn bị gặp nhiều lỗi lúc đầu; trang strict mode liệt kê những gì mỗi cờ kiểm tra để bạn bật lần lượt từng cờ.

Câu hỏi thường gặp

Điểm khác nhau chính giữa TypeScript và JavaScript là gì?

TypeScript bổ sung kiểu tĩnh cho JavaScript. Bạn mô tả mỗi giá trị là gì (name: string, items: Item[]), và trình biên dịch TypeScript báo lỗi trước khi code chạy. JavaScript không kiểm tra gì trước: một kiểu sai chỉ lộ ra khi dòng đó được thực thi, thường dưới dạng undefined hoặc một TypeError.

TypeScript có tốt hơn JavaScript không?

Với hầu hết dự án do nhiều người cùng duy trì, hoặc tồn tại lâu hơn vài tuần, câu trả lời là có: kiểu bắt được cả nhóm bug, giúp refactor an toàn và là nền tảng cho tính năng tự động gợi ý code trong editor. Với một script ngắn, một bản prototype nhanh hay một bài tập khi học, JavaScript thuần bắt đầu nhanh hơn và không cần thiết lập build.

TypeScript có chạy nhanh hơn JavaScript không?

Không, và cũng không chậm hơn. TypeScript được biên dịch sang JavaScript và các kiểu bị xóa đi, nên code thực sự chạy chính là JavaScript mà bạn tự viết tay. Chi phí thêm duy nhất là thời gian biên dịch trong lúc phát triển.

Nên học JavaScript hay TypeScript trước?

Hãy học JavaScript cơ bản trước, hoặc học song song với TypeScript. Mọi hành vi lúc chạy (biến, hàm, đối tượng, mảng, promise) đều là JavaScript, TypeScript chỉ mô tả chúng. Khi bạn đã viết được các chương trình JavaScript nhỏ, việc thêm kiểu chỉ là một bước ngắn.

Có thể dùng TypeScript và JavaScript trong cùng một dự án không?

Có. Đặt "allowJs": true trong tsconfig.json và trình biên dịch sẽ chấp nhận tệp .js bên cạnh tệp .ts. Thêm "checkJs": true (hoặc chú thích // @ts-check ở từng tệp) để kiểm tra kiểu cả các tệp JavaScript, dùng kiểu lấy từ chú thích JSDoc. Đây là cách thường dùng để chuyển dự án sang TypeScript từng tệp một.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu