cocos creator学习之typeScript常见语法问题
1. TypeScript 和 JavaScript 的区别是什么?
(1) 类型检查时机不同
TypeScript 编译时(代码编写后、运行前),JavaScript 运行时(代码执行到对应语句时)
TypeScript:编译时直接报错,提前拦截问题
function add(a: number, b: number) {return a + b;
}
// 报错: Argument of type 'string' is not assignable to parameter of type 'number'
add(1, "2");
JavaScript:运行时才报错,若未执行到该代码则不会发现问题
function add(a, b) {return a + b;
}
// 运行时不报错,但返回非预期结果 "12"(字符串拼接)
console.log(add(1, "2"));
// 若调用时参数类型错误且未执行,代码无任何提示
(2)类型的声明方式
TypeScript 支持显式声明(如 let a: number = 1
),也支持自动类型推断
JavaScript 无显式声明,变量类型随赋值动态变化
(3) 语法特性差异:TypeScript 新增核心能力
TypeScript 在 JavaScript 语法基础上,新增了一系列围绕 “类型” 的特性,这些特性在 JavaScript 中完全不存在。
--- 枚举(Enum) 定义一组命名的常量,提高代码可读性和可维护性(JavaScript 需通过对象模拟)。
// 数字枚举(默认从 0 开始递增)
enum Direction {Up, // 0Down, // 1Left, // 2Right // 3
}// 字符串枚举
enum Status {Pending = "PENDING",Success = "SUCCESS",Error = "ERROR"
}const currentStatus: Status = Status.Success;
--- 元组 (Tuple)
限定数组的长度和每个元素的类型(JavaScript 数组元素类型可任意混合)。
// 元组:第一个元素为 string,第二个为 number
let user: [string, number] = ["Bob", 30];
user = [123, "Alice"]; // 报错:类型不匹配
--- 接口(Interface) 定义对象的结构契约,强制实现者遵循约定的属性和方法(JavaScript 无接口概念,需通过注释或逻辑约束)。
--- 类型别名(Type)为任意类型定义别名,简化复杂类型的复用。
// 为基础类型定义别名
type Age = number;
let userAge: Age = 28;// 为联合类型定义别名
type StringOrNumber = string | number;
let value: StringOrNumber = "test";
value = 123; // 合法// 为对象类型定义别名
type Point = { x: number; y: number };
const p: Point = { x: 10, y: 20 };
--- 泛型语法的支持, 实现类型的 “参数化”,创建可复用的组件(如函数、类),适配多种类型(JavaScript 需通过 any
牺牲类型安全)。
// 泛型函数:返回与输入相同类型的值
function identity<T>(arg: T): T {return arg;
}const num: number = identity(42);
const str: string = identity("hello");// 泛型类
class Container<T> {private value: T;constructor(value: T) {this.value = value;}getValue(): T {return this.value;}
}const stringContainer = new Container("test");
--- 高级类型操作, 联合类型(Union Types),交叉类型(Intersection Types),映射类型(Mapped Types)
2. 接口(interface
)和类型别名(type
)的区别?
在 TypeScript 中,interface
(接口)和 type
(类型别名)都可以用来定义复杂类型,但它们在功能、使用场景和行为上存在一些关键区别。
(1) 基本语法定义
interface
:专门用于定义对象的结构,使用 interface
关键字。
interface User {name: string;age: number;
}
type
:可以为任意类型(包括基本类型、联合类型、交叉类型等)定义别名,使用 type
关键字。
// 为对象类型定义别名
type User = {name: string;age: number;
};// 为基本类型定义别名
type Age = number;// 为联合类型定义别名
type Status = "pending" | "success" | "error";
(2)扩展(继承)能力
interface
:支持通过 extends
关键字扩展其他接口或类型,实现继承。
interface Person {name: string;
}// 扩展 Person 接口
interface User extends Person {age: number;
}const user: User = {name: "Alice",age: 30 // 必须包含扩展的属性
};
type
:不支持 extends
,但可以通过交叉类型(&
)实现类似扩展的效果。
type Person = {name: string;
};// 通过交叉类型扩展
type User = Person & {age: number;
};const user: User = {name: "Bob",age: 25
};
(3) 合并声明(声明合并)
interface
:支持声明合并,即多次定义同名接口会自动合并属性。
// 第一次定义
interface User {name: string;
}// 第二次定义,会与第一次合并
interface User {age: number;
}// 合并后:{ name: string; age: number }
const user: User = {name: "Charlie",age: 35
};
type
:不支持声明合并,同名的 type
会报错。
type User = { name: string };
type User = { age: number }; // 报错:标识符“User”重复
interface
不能定义基本类型的别名,而 type
可以:
type Age = number; // 合法
interface Age = number; // 报错:语法错误
3. 如何定义可选属性?
使用 ?
标记可选属性。
interface Person {name: string;age?: number; // 可选
}const p: Person = { name: "Alice" }; // 合法type Student = {name: string,age?: number //可选
}
4. 类型守卫(Type Guard)是什么?如何实现?
使用 typeof
或自定义谓词函数。
function isNumber(x: any): x is number {return typeof x === "number";
}function processValue(value: number | string): void {if (isNumber(value)) {console.log(value.toFixed(2));} else {console.log(value.toUpperCase());}
}
5. any
和 unknown
的区别?
在 TypeScript 中,any
和 unknown
都用于表示 “类型不确定” 的值,但它们的类型安全性有本质区别,这直接影响了代码的健壮性。
核心区别:类型检查的严格程度
any
:完全关闭类型检查,允许对其执行任何操作,不进行任何类型验证。
unknown
:开启严格类型检查,必须先确认类型后才能执行操作,是 “类型安全的 any
”。
--- any
:可以对 any
类型的值执行任何操作(调用方法、访问属性等),TypeScript 不会报错。
let value: any = "hello";
value.toUpperCase(); // 允许(正确操作)
value.foo(); // 允许(即使 foo 方法不存在,也不报错)
value = 42;
value.toFixed(2); // 允许
value = true;
value.toExponential(); // 允许(编译时不报错,运行时可能出错)
--- unknown
:不能直接对 unknown
类型的值执行操作,必须先通过类型检查确认其具体类型。
let value: unknown = "hello";
value.toUpperCase(); // 报错:对象的类型为 "unknown"// 必须先做类型判断
if (typeof value === "string") {value.toUpperCase(); // 允许(此时 TypeScript 知道是 string 类型)
}value = 42;
value.toFixed(2); // 报错:对象的类型为 "unknown"
if (typeof value === "number") {value.toFixed(2); // 允许
}
6. 装饰器(Decorator)是什么?如何使用?
TypeScript 中的装饰器(Decorator)是一种特殊类型的声明,它能够修改类、方法、属性或参数的行为。装饰器基于 JavaScript 的元编程(MetaProgramming)特性,允许在不修改原有代码结构的前提下,为代码添加额外功能(如日志记录、性能统计、权限校验等)。
装饰器目前是 TypeScript 的实验性特性,需要在 tsconfig.json
中启用配置才能使用:
{"compilerOptions": {"target": "ES5","experimentalDecorators": true, // 启用装饰器"emitDecoratorMetadata": true // 可选,生成装饰器元数据}
}
装饰器按作用目标可分为类装饰器、方法装饰器、属性装饰器、参数装饰器,它们的语法都是在目标前加上 @装饰器名称
。
(1)类装饰器(Class Decorators)
作用于类本身,用于修改类的行为(如添加方法、修改构造函数等)。
语法:接收一个参数(类的构造函数),返回值可以是新的构造函数(用于替换原类)。
// 定义类装饰器:为类添加静态属性和实例方法
function addMetadata(constructor: Function) {// 添加静态属性constructor.prototype.version = "1.0.0";// 添加实例方法constructor.prototype.log = function() {console.log(`Class name: ${this.constructor.name}`);};
}// 应用装饰器
@addMetadata
class User {name: string;constructor(name: string) {this.name = name;}
}// 使用装饰器添加的功能
const user = new User("Alice");
console.log(user.version); // 1.0.0(装饰器添加的属性)
user.log(); // Class name: User(装饰器添加的方法)
(2)方法装饰器(Method Decorators)
作用于类的方法,用于修改方法的行为(如添加日志、缓存结果、权限校验等)。
语法:接收三个参数:
target
:对于静态方法是类本身,对于实例方法是类的原型对象propertyKey
:方法名称descriptor
:方法的属性描述符(包含value
、writable
等)
// 定义方法装饰器:打印方法调用日志
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {// 保存原方法const originalMethod = descriptor.value;// 重写方法descriptor.value = function(...args: any[]) {console.log(`调用方法 ${propertyKey},参数:`, args);const result = originalMethod.apply(this, args); // 执行原方法console.log(`方法 ${propertyKey} 返回值:`, result);return result;};return descriptor;
}class Calculator {// 应用方法装饰器@logMethodadd(a: number, b: number): number {return a + b;}
}const calc = new Calculator();
calc.add(2, 3);
// 输出:
// 调用方法 add,参数: [2, 3]
// 方法 add 返回值: 5
(3)属性装饰器(Property Decorators)
作用于类的属性,用于修改属性的行为(如添加默认值、验证规则等)。
语法:接收两个参数(与方法装饰器前两个参数相同),返回值会被忽略。
// 定义属性装饰器:为属性设置默认值
function defaultValue(value: any) {return function(target: any, propertyKey: string) {target[propertyKey] = value; // 设置默认值};
}class Person {// 应用属性装饰器@defaultValue("Unknown")name: string;@defaultValue(18)age: number;
}const person = new Person();
console.log(person.name); // Unknown(装饰器设置的默认值)
console.log(person.age); // 18(装饰器设置的默认值)
7. 可索引类型(Indexable Types)
TypeScript 的可索引类型(Indexable Types) 用于描述那些可以通过索引访问的对象(如数组、对象字面量),它定义了通过索引(如数字索引、字符串索引)访问时返回值的类型。
可索引类型的核心是索引签名(Index Signature),通过它可以约束:
- 索引的类型(只能是
string
或number
) - 通过索引访问返回值的类型
(1)字符串索引签名(最常用)
用于描述通过字符串索引访问的对象(如普通对象),表示当使用字符串作为索引时,返回值的类型。
// 定义一个具有字符串索引签名的接口
interface StringMap {// 字符串索引签名:key 是 string 类型,返回值是 number 类型[key: string]: number;
}// 符合接口的对象
const counts: StringMap = {apple: 5,banana: 10,// "orange": "many" // 报错:返回值必须是 number 类型
};// 通过字符串索引访问
console.log(counts["apple"]); // 5(类型为 number)
console.log(counts.banana); // 10(类型为 number)
(2) 数字索引签名
用于描述通过数字索引访问的对象(如数组),表示当使用数字作为索引时,返回值的类型。
// 定义一个具有数字索引签名的接口
interface NumberArray {// 数字索引签名:index 是 number 类型,返回值是 string 类型[index: number]: string;
}// 符合接口的数组
const fruits: NumberArray = ["apple", "banana", "cherry"];// 通过数字索引访问
console.log(fruits[0]); // "apple"(类型为 string)
console.log(fruits[1]); // "banana"(类型为 string)
(3)用类型别名定义可索引类型
除了接口,也可以用 type
定义可索引类型:
type NumberMap = {[key: string]: number;
};type StringArray = {[index: number]: string;
};
8. 如何使用 never
类型?
在 TypeScript 中,never
类型表示永远不会发生的值的类型。它描述的是那些根本不可能存在的状态,通常用于表示函数不会正常返回、变量永远不会被赋值等场景。
--- never
是所有类型的子类型,可以赋值给任何类型
--- 没有任何类型是 never
的子类型(除了 never
本身),因此不能将其他类型赋值给 never
类型的变量
(1) 永不返回的函数(终止函数)
当函数抛出异常或进入无限循环(永远不会执行完)时,其返回值类型为 never
。
// 抛出异常的函数
function throwError(message: string): never {throw new Error(message); // 函数不会正常返回
}// 无限循环的函数
function infiniteLoop(): never {while (true) {// 循环永远不会结束}
}
这类函数无法被正常执行完毕,因此 TypeScript 推断其返回类型为 never
而非 void
(void
表示函数正常执行完毕但没有返回值)。
(2) 表示不可能被赋值的变量
如果变量的类型是 never
,则它永远不能被赋值(除了 never
本身)。
let impossible: never;impossible = 123; // 报错:不能将 number 类型赋值给 never 类型
impossible = "hello"; // 报错:不能将 string 类型赋值给 never 类型
impossible = throwError("出错了"); // 合法:throwError 返回 never 类型