JavaScript对比TypeScript
作为一名 JavaScript 工程师,我经常被问到:"为什么要使用 TypeScript?"或者"TypeScript 相比 JavaScript 有什么优势?"今天,让我们通过实际的代码示例来深入探讨这个话题。
核心特性对比
1. 类型系统:最显著的区别
function calculateTotal(items) {
  return items.reduce((total, item) => total + item.price, 0);
}
const items = [
  { price: 10 },
  { price: 20 },
  { notPrice: 30 }  
];
console.log(calculateTotal(items)); 
interface Item {
  price: number;
}
function calculateTotal(items: Item[]): number {
  return items.reduce((total, item) => total + item.price, 0);
}
const items = [
  { price: 10 },
  { price: 20 },
  { notPrice: 30 }  
];
2. 接口和类型定义
const user = {
  name: 'John',
  age: 30,
  address: {
    street: '123 Main St',
    city: 'Boston'
  }
};
function updateUser(user) {
  
  user.name = 'Jane';
}
interface Address {
  street: string;
  city: string;
  zipCode?: string; 
}
interface User {
  name: string;
  age: number;
  address: Address;
}
function updateUser(user: User): void {
  user.name = 'Jane'; 
}
3. 函数重载
function process(input) {
  if (typeof input === 'string') {
    return input.toUpperCase();
  } else if (Array.isArray(input)) {
    return input.map(item => item.toUpperCase());
  }
  throw new Error('Unsupported input type');
}
function process(input: string): string;
function process(input: string[]): string[];
function process(input: string | string[]): string | string[] {
  if (typeof input === 'string') {
    return input.toUpperCase();
  } else {
    return input.map(item => item.toUpperCase());
  }
}
4. 泛型
function firstElement(arr) {
  return arr[0];
}
const numResult = firstElement([1, 2, 3]);
const strResult = firstElement(['a', 'b', 'c']);
function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}
const numResult = firstElement([1, 2, 3]); 
const strResult = firstElement(['a', 'b', 'c']); 
TypeScript 特有的语法特性
1. 类型注解(Type Annotations)
let name = "John";
let age = 30;
let isStudent = true;
let numbers = [1, 2, 3];
let tuple = ["hello", 10];
 * @param {string} name
 * @returns {string}
 */
function greet(name) {
    return `Hello, ${name}!`;
}
let name: string = "John";
let age: number = 30;
let isStudent: boolean = true;
let numbers: number[] = [1, 2, 3];
let tuple: [string, number] = ["hello", 10];
function greet(name: string): string {
    return `Hello, ${name}!`;
}
2. 枚举(Enums)
const Direction = {
    Up: "UP",
    Down: "DOWN",
    Left: "LEFT",
    Right: "RIGHT",
    
    Object.freeze(Direction);
};
const Direction = {
    Up: Symbol("UP"),
    Down: Symbol("DOWN"),
    Left: Symbol("LEFT"),
    Right: Symbol("RIGHT")
};
let playerDirection = Direction.Up;
const StatusCode = {
    OK: 200,
    NotFound: 404,
    Error: 500,
    Object.freeze(StatusCode);
};
enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT"
}
let playerDirection: Direction = Direction.Up;
enum StatusCode {
    OK = 200,
    NotFound = 404,
    Error = 500
}
3. 类型断言(Type Assertions)
let someValue = "this is a string";
let strLength = someValue.length;
if (typeof someValue === 'string') {
    let strLength = someValue.length;
}
const myCanvas = document.getElementById('main_canvas');
if (myCanvas instanceof HTMLCanvasElement) {
    
    const ctx = myCanvas.getContext('2d');
}
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
let strLength: number = (<string>someValue).length;
const myCanvas = document.getElementById('main_canvas') as HTMLCanvasElement;
4. 访问修饰符
class Employee {
    #name; 
    _age; 
    department;
    constructor(name, age, department, id) {
        this.#name = name;
        this._age = age;
        this.department = department;
        Object.defineProperty(this, 'id', {
            value: id,
            writable: false 
        });
    }
    #getDetails() { 
        return `${this.#name} (${this._age})`;
    }
}
class Employee {
    private name: string;
    protected age: number;
    public department: string;
    readonly id: number;
    constructor(name: string, age: number, department: string, id: number) {
        this.name = name;
        this.age = age;
        this.department = department;
        this.id = id;
    }
    private getDetails(): string {
        return `${this.name} (${this.age})`;
    }
}
5. 抽象类和接口
class Animal {
    constructor() {
        if (new.target === Animal) {
            throw new Error('Animal is abstract');
        }
    }
    makeSound() {
        throw new Error('makeSound must be implemented');
    }
    move() {
        console.log("Moving...");
    }
}
class Pet {
    constructor() {
        if (this.play === undefined) {
            throw new Error('Must implement play method');
        }
        if (!this.name) {
            throw new Error('Must have name property');
        }
    }
}
class Dog extends Animal {
    constructor(name) {
        super();
        this.name = name;
    }
    makeSound() {
        console.log("Woof!");
    }
    play() {
        console.log("Playing fetch!");
    }
}
abstract class Animal {
    abstract makeSound(): void;
    move(): void {
        console.log("Moving...");
    }
}
interface Pet {
    name: string;
    play(): void;
}
class Dog extends Animal implements Pet {
    name: string;
    
    constructor(name: string) {
        super();
        this.name = name;
    }
    makeSound(): void {
        console.log("Woof!");
    }
    play(): void {
        console.log("Playing fetch!");
    }
}
6. 联合类型和交叉类型
function processValue(value) {
    if (typeof value === "string") {
        return value.toUpperCase();
    }
    if (typeof value === "number") {
        return value * 2;
    }
    throw new Error('Invalid type');
}
const person = {
    ...{ name: "John" },
    ...{ age: 30 }
};
type StringOrNumber = string | number;
type NameAndAge = { name: string } & { age: number };
function processValue(value: StringOrNumber) {
    if (typeof value === "string") {
        return value.toUpperCase();
    }
    return value * 2;
}
const person: NameAndAge = {
    name: "John",
    age: 30
};
7. 可选链和空值合并
const user = {
    name: "John"
};
const city = user.address?.city;
const street = user.address?.street ?? "Default Street";
const city = user && user.address && user.address.city;
const street = (user && user.address && user.address.street) || "Default Street";
interface User {
    name: string;
    address?: {
        street?: string;
        city?: string;
    };
}
const user: User = {
    name: "John"
};
const city = user.address?.city;
const street = user.address?.street ?? "Default Street";
8. 字面量类型
const CARDINAL_DIRECTIONS = ["North", "South", "East", "West"];
const DICE_VALUES = [1, 2, 3, 4, 5, 6];
function move(direction) {
    if (!CARDINAL_DIRECTIONS.includes(direction)) {
        throw new Error('Invalid direction');
    }
    console.log(`Moving ${direction}`);
}
move("North"); 
move("Northeast"); 
type CardinalDirection = "North" | "South" | "East" | "West";
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
function move(direction: CardinalDirection) {
    console.log(`Moving ${direction}`);
}
move("North"); 
9. 类型别名和映射类型
const createPoint = (x, y) => ({
    x,
    y
});
const createReadonlyPoint = (x, y) => 
    Object.freeze({
        x,
        y
    });
const point = createReadonlyPoint(10, 20);
type Point = {
    x: number;
    y: number;
};
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};
type ReadonlyPoint = Readonly<Point>;
const point: ReadonlyPoint = { x: 10, y: 20 };
10. 装饰器(Decorators)
function log(target, propertyKey) {
    console.log(`Accessing property: ${propertyKey}`);
}
class Example {
    @log
    name = "example";
}
function makeLoggable(target) {
    const originalDescriptor = Object.getOwnPropertyDescriptor(target.prototype, 'name');
    Object.defineProperty(target.prototype, 'name', {
        get() {
            console.log('Accessing property: name');
            return originalDescriptor.get.call(this);
        },
        set(value) {
            console.log('Setting property: name');
            originalDescriptor.set.call(this, value);
        }
    });
    return target;
}
@makeLoggable
class Example {
    name = "example";
}
function log(target: any, propertyKey: string) {
    console.log(`Accessing property: ${propertyKey}`);
}
class Example {
    @log
    name: string = "example";
}
这些语法特性使得 TypeScript 能够:
- 提供更强大的类型检查和编译时验证
- 支持面向对象编程的高级特性
- 提供更好的代码组织和重用机制
- 增强代码的可读性和可维护性
- 提供更好的 IDE 支持和开发体验
虽然很多特性在现代 JavaScript 中也可以实现,但实现方式往往更复杂,且缺少编译时的类型检查。TypeScript 的优势在于它提供了更简洁、更安全的语法,以及强大的类型系统支持。
如何在项目中使用 TypeScript
1. 初始化项目
mkdir my-ts-project
cd my-ts-project
npm init -y
npm install typescript --save-dev
npx tsc --init
2. 配置 tsconfig.json
{
  "compilerOptions": {
    "target": "es2020",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
3. 项目结构
my-ts-project/
├── src/
│   └── index.ts
├── package.json
├── tsconfig.json
└── node_modules/
4. 开发工作流
- 编写 TypeScript 代码(.ts 文件)
- 使用 tsc 编译代码:npx tsc
- 运行编译后的 JavaScript 代码:node dist/index.js
5. 推荐的开发工具
- VS Code:内置 TypeScript 支持
- ESLint 配置:
npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint
总结
TypeScript 相比 JavaScript 的主要优势:
- 静态类型检查,提前发现潜在错误
- 更好的 IDE 支持,包括代码补全和重构
- 接口和类型定义提供更清晰的代码契约
- 更容易维护大型项目
- 通过类型推断减少文档需求
虽然需要一些学习成本,但 TypeScript 带来的好处远超过这些成本,特别是在大型项目中。作为一个 JavaScript 工程师,掌握 TypeScript 将显著提升你的开发效率和代码质量。