TypeScript 基本语法:变量声明与数据类型详解
变量声明
在 TypeScript 中,变量声明是编程的基础操作之一,它允许我们在程序中存储和使用数据。TypeScript 提供了几种方式来声明变量,每种方式都有其特点和适用场景。
let 关键字
let
是 TypeScript 中用于声明块级作用域变量的关键字。与 JavaScript 中的 var
不同,let
声明的变量只在其所在的块级作用域内有效。块级作用域可以是 if
语句块、for
循环块、函数块等。
{
let message: string = "Hello, TypeScript!";
console.log(message); // 输出: Hello, TypeScript!
}
// console.log(message); // 这里会报错,message 超出了作用域
在上述代码中,message
变量在花括号内声明,其作用域仅限于这个花括号块。当在块外尝试访问 message
时,TypeScript 编译器会报错。
const 关键字
const
用于声明常量,一旦声明,其值就不能再被修改。常量同样具有块级作用域。
const PI: number = 3.14159;
// PI = 3.14; // 这会导致编译错误,常量不能重新赋值
在实际开发中,const
常用于声明那些在程序运行过程中不会改变的值,如数学常量、配置参数等。这有助于提高代码的可读性和可维护性,同时也能让编译器进行一些优化。
var 关键字
虽然 TypeScript 推荐使用 let
和 const
,但它仍然支持传统 JavaScript 的 var
关键字。var
声明的变量具有函数作用域,而不是块级作用域。这意味着在函数内部声明的 var
变量在整个函数内都有效,即使在块级作用域之外。
function varScopeTest() {
if (true) {
var localVar: string = "Inside if block";
}
console.log(localVar); // 输出: Inside if block
}
varScopeTest();
从上面的代码可以看出,localVar
虽然在 if
块内声明,但在块外仍然可以访问,这是因为 var
的函数作用域特性。然而,这种特性有时会导致意外的行为,所以在 TypeScript 中应尽量避免使用 var
。
数据类型
TypeScript 是一种强类型语言,它支持多种数据类型,这使得代码在编译阶段就能发现类型不匹配的错误,从而提高代码的稳定性和可维护性。
基本数据类型
TypeScript 支持 JavaScript 的所有基本数据类型,包括 number
、string
、boolean
、null
、undefined
和 void
。
number
number
类型用于表示数字,包括整数和浮点数。
let age: number = 25;
let pi: number = 3.14;
TypeScript 中的 number
类型与 JavaScript 中的 Number
类型一致,它遵循 IEEE 754 标准,能够表示双精度 64 位浮点数。
string
string
类型用于表示文本数据。字符串可以用单引号 '
或双引号 "
来表示。
let name: string = 'John Doe';
let greeting: string = "Hello, World!";
TypeScript 还支持模板字符串,通过反引号 ``` 来定义。模板字符串允许在字符串中嵌入表达式。
let age: number = 30;
let message: string = `My age is ${age}`;
console.log(message); // 输出: My age is 30
boolean
boolean
类型只有两个值:true
和 false
,用于表示逻辑判断。
let isDone: boolean = false;
if (isDone) {
console.log('Task is completed');
} else {
console.log('Task is still in progress');
}
null 和 undefined
null
和 undefined
在 TypeScript 中分别表示空值和未定义值。它们是所有类型的子类型。默认情况下,null
和 undefined
可以赋值给任何类型的变量。
let value1: string | null = null;
let value2: number | undefined = undefined;
然而,在严格模式下(strictNullChecks
开启),null
和 undefined
只能赋值给 void
或它们自身类型。
// 开启 strictNullChecks
let value3: string;
// value3 = null; // 这会导致编译错误
void
void
类型通常用于表示函数没有返回值。
function logMessage(message: string): void {
console.log(message);
}
在上述代码中,logMessage
函数只负责打印消息,没有返回值,所以其返回类型为 void
。
复杂数据类型
除了基本数据类型,TypeScript 还支持一些复杂数据类型,如数组、元组、对象、枚举等。
数组
数组是一种有序的数据集合,TypeScript 提供了两种方式来声明数组。
- 类型 + 方括号
let numbers: number[] = [1, 2, 3, 4, 5];
在这种方式中,number[]
表示这是一个元素类型为 number
的数组。
- Array 泛型
let fruits: Array<string> = ['apple', 'banana', 'cherry'];
这里 Array<string>
同样表示一个字符串类型的数组。
数组的长度是可变的,我们可以通过索引来访问和修改数组元素。
let numbers: number[] = [1, 2, 3];
console.log(numbers[0]); // 输出: 1
numbers[1] = 20;
console.log(numbers); // 输出: [1, 20, 3]
元组
元组是一种特殊的数组,它允许存储不同类型的元素,并且元素的数量和类型在声明时就固定下来。
let user: [string, number] = ['John', 30];
在上述代码中,user
元组的第一个元素是 string
类型,第二个元素是 number
类型。如果尝试给元组赋不符合类型或数量的值,会导致编译错误。
// user = ['John']; // 错误,元素数量不足
// user = ['John', 30, 'Male']; // 错误,元素数量过多
// user = [30, 'John']; // 错误,元素类型不匹配
元组可以通过索引访问元素,也可以使用解构赋值来提取元素。
let user: [string, number] = ['John', 30];
let [name, age] = user;
console.log(name); // 输出: John
console.log(age); // 输出: 30
对象
对象是一种无序的键值对集合。在 TypeScript 中,我们可以定义对象的类型,指定每个属性的名称和类型。
let person: { name: string; age: number } = { name: 'Jane', age: 28 };
在上述代码中,person
对象必须包含 name
属性,类型为 string
,以及 age
属性,类型为 number
。如果对象缺少某个属性或属性类型不匹配,会导致编译错误。
// let wrongPerson: { name: string; age: number } = { name: 'Bob' }; // 错误,缺少 age 属性
// let wrongPerson: { name: string; age: number } = { name: 'Bob', age: 'twenty' }; // 错误,age 类型不匹配
对象还可以有可选属性,通过在属性名后加 ?
来表示。
let user: { name: string; age?: number } = { name: 'Alice' };
// 这里 user 可以没有 age 属性
枚举
枚举是一种用于定义命名常量集合的类型。它可以让我们用更具描述性的名称来表示数值。
enum Color {
Red,
Green,
Blue
}
let myColor: Color = Color.Green;
console.log(myColor); // 输出: 1
在上述代码中,Color
枚举定义了三个常量 Red
、Green
和 Blue
。默认情况下,它们的值从 0 开始依次递增。我们也可以手动指定枚举成员的值。
enum Direction {
Up = 1,
Down,
Left,
Right
}
let myDirection: Direction = Direction.Right;
console.log(myDirection); // 输出: 4
枚举在需要使用一组相关常量的场景中非常有用,比如表示状态码、方向等。
类型推论
TypeScript 具有类型推论机制,这意味着在某些情况下,我们不需要显式地指定变量的类型,TypeScript 编译器可以根据变量的初始值推断出其类型。
let num = 10; // TypeScript 推断 num 为 number 类型
let str = 'Hello'; // TypeScript 推断 str 为 string 类型
类型推论在函数返回值的推断中也很常见。
function add(a, b) {
return a + b;
}
let result = add(5, 3); // result 被推断为 number 类型
然而,在某些复杂情况下,显式地指定类型可以使代码更清晰,也有助于避免潜在的错误。
类型断言
类型断言是一种告诉编译器“我知道这个值的类型是什么”的方式。当我们比编译器更清楚某个值的类型时,可以使用类型断言。
let someValue: any = 'this is a string';
let strLength: number = (someValue as string).length;
在上述代码中,someValue
的类型是 any
,我们通过类型断言 (someValue as string)
告诉编译器 someValue
实际上是一个字符串,这样就可以访问其 length
属性。
类型断言还有另一种语法 (<type>value)
,但在 JSX 中只能使用 as
语法。
let someValue: any = 'this is a string';
let strLength: number = (<string>someValue).length;
联合类型与交叉类型
联合类型
联合类型允许一个变量具有多种类型中的一种。我们使用 |
来分隔不同的类型。
let value: string | number;
value = 'Hello';
console.log(value.length); // 输出: 5
value = 10;
// console.log(value.length); // 这会导致编译错误,number 类型没有 length 属性
在上述代码中,value
可以是 string
类型或 number
类型。当 value
为 string
类型时,可以访问 length
属性,但当 value
为 number
类型时,访问 length
属性会导致编译错误。
在使用联合类型时,我们需要注意只能访问联合类型中所有类型共有的属性和方法。
function printValue(val: string | number) {
// 这里只能访问 string 和 number 共有的属性和方法,如 toString()
console.log(val.toString());
}
printValue('Hello'); // 输出: Hello
printValue(10); // 输出: 10
交叉类型
交叉类型用于将多个类型合并为一个类型,新类型具有所有交叉类型的特性。我们使用 &
来表示交叉类型。
interface A {
name: string;
}
interface B {
age: number;
}
let person: A & B = { name: 'Tom', age: 25 };
在上述代码中,person
类型是 A
和 B
的交叉类型,所以它必须同时包含 name
属性(类型为 string
)和 age
属性(类型为 number
)。
交叉类型常用于需要一个对象同时满足多个接口定义的场景,它可以让我们更灵活地组合不同的类型定义。
类型别名与接口
类型别名
类型别名是给一个类型起一个新的名字,它可以是基本类型、复杂类型或联合类型等。
type MyNumber = number;
let num: MyNumber = 42;
在上述代码中,MyNumber
是 number
类型的别名,num
变量的类型实际上就是 number
。
类型别名对于联合类型和复杂类型的定义非常有用,可以提高代码的可读性。
type StringOrNumber = string | number;
let value: StringOrNumber = 'Hello';
value = 10;
类型别名也可以用于定义函数类型。
type AddFunction = (a: number, b: number) => number;
let add: AddFunction = function (a, b) {
return a + b;
};
接口
接口用于定义对象的形状,它描述了对象应该具有哪些属性以及这些属性的类型。
interface User {
name: string;
age: number;
}
let user: User = { name: 'John', age: 30 };
接口可以有可选属性和只读属性。
interface Product {
name: string;
price: number;
description?: string; // 可选属性
readonly id: number; // 只读属性
}
let product: Product = { name: 'Book', price: 20, id: 1 };
// product.id = 2; // 这会导致编译错误,id 是只读属性
接口还可以继承其他接口,以扩展其定义。
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
let myDog: Dog = { name: 'Buddy', breed: 'Golden Retriever' };
在上述代码中,Dog
接口继承了 Animal
接口,所以 Dog
接口除了有自己的 breed
属性外,还必须包含 Animal
接口的 name
属性。
函数类型
在 TypeScript 中,函数也有类型。函数类型由参数类型和返回值类型组成。
function add(a: number, b: number): number {
return a + b;
}
在上述代码中,add
函数的参数 a
和 b
都是 number
类型,返回值也是 number
类型。
我们还可以使用类型别名来定义函数类型,然后用这个类型别名来声明函数。
type AddFunction = (a: number, b: number) => number;
let add: AddFunction = function (a, b) {
return a + b;
};
函数参数可以有默认值,并且默认值参数后面的参数也可以省略。
function greet(name: string = 'Guest') {
console.log(`Hello, ${name}`);
}
greet(); // 输出: Hello, Guest
greet('John'); // 输出: Hello, John
剩余参数(rest 参数)允许我们将多个参数收集到一个数组中。
function sum(...numbers: number[]) {
return numbers.reduce((acc, num) => acc + num, 0);
}
let result = sum(1, 2, 3, 4, 5);
console.log(result); // 输出: 15
总结
通过深入了解 TypeScript 的变量声明和数据类型,我们可以编写出更健壮、更易于维护的前端代码。变量声明的不同方式,如 let
、const
和 var
,各自适用于不同的场景,合理选择可以避免许多潜在的错误。丰富的数据类型,从基本数据类型到复杂数据类型,再到联合类型、交叉类型等,为我们精确描述程序中的数据提供了强大的工具。同时,类型推论、类型断言、类型别名、接口以及函数类型等特性,进一步增强了 TypeScript 的类型系统,使我们能够在编译阶段就捕获许多类型相关的错误,从而提高开发效率和代码质量。在实际项目中,灵活运用这些知识,将有助于我们打造高质量的前端应用。