记录 TypeScript

63 阅读17分钟

记录TypeScript,详情请看文档TypeScript 中文文档 (nodejs.cn) 或者 TypeScript 中文手册 - TypeScript 中文手册 (bootcss.com)

JavaScript 与 TypeScript 的区别

TypeScript 是 JavaScript 的超集,扩展了 JavaScript 的语法,因此现有的 JavaScript 代码可与 TypeScript 一起工作无需任何修改,TypeScript 通过类型注解提供编译时的静态类型检查。 TypeScript 可处理已有的 JavaScript 代码,并只对其中的 TypeScript 代码进行编译。

TypeScript基础类型

数据类型关键字描述
任意类型any声明any的变量可以赋予任意类型的值
数字类型number双精度 64 位浮点值。它可以用来表示整数和分数
字符串类型string一个字符系列,使用单引号(')或双引号(")来表示字符串类型。反引号(`)来定义多行文本和内嵌表达式。
布尔类型boolean表示逻辑值:true 和 false。
数组类型声明变量为数组。
元组元组类型用来表示已知元素数量和类型的数组,各元素的类型不必相同,对应位置的类型需要相同
枚举enum枚举类型用于定义数值集
voidvoid用于标识方法返回值的类型,表示该方法没有返回值。
nullnull表示对象值缺失
undefinedundefined用于初始化变量为一个未定义的值
nevernevernever 是其它类型(包括 null 和 undefined)的子类型,代表从不会出现的值。

注意: TypeScript 和 JavaScript 没有整数类型。

// TypeScript 提供了一些基本的数据类型,你可以用来定义变量
let isDone: boolean = false; // 布尔值
let age: number = 30; // 数字
let name: string = "Alice";   // 字符串
let list: number[] = [1, 2, 3]; // 数组
let tuple: [string, number] = ["Bob", 25]; // 元组
//any
let x: any = 1;    // 数字类型
x = 'I am who I am';    // 字符串类型
x = false;    // 布尔类型
let arrayList: any[] = [1, false, 'fine'];
arrayList[1] = 100;

// number
let binaryLiteral: number = 0b1010; // 二进制
let octalLiteral: number = 0o744;    // 八进制
let decLiteral: number = 6;    // 十进制
let hexLiteral: number = 0xf00d;    // 十六进制

//sting
let name: string = "Runoob";
let years: number = 5;
let words: string = `您好,今年是 ${ name } 发布 ${ years + 1} 周年`;

// boolean
let flag: boolean = true;

//Array
// 在元素类型后面加上[]  写法: let 变量: 类型[] = [值1,...]:
let arr: number[] = [1, 2];
let arr: [string, number] = ["1", 2];
// 或者使用数组泛型  写法: let 变量: Array<类型> = [值1,...]
let arr: Array<number> = [1, 2];

// 元组
let x: [string, number];
x = ['Runoob', 1];    // 运行正常
x = [1, 'Runoob'];    // 报错
console.log(x[0]);    // 输出 Runoob

// 枚举
enum Color {Red, Green, Blue};
let c: Color = Color.Blue;
console.log(c);    // 输出 2

// void
function hello(): void {
   alert("Hello Runoob");
}

类型断言(Type Assertion)

类型断言可以用来手动指定一个值的类型,即允许变量从一种类型更改为另一种类型。

语法格式:
<类型>值 或 as 类型

var str = '1' 
var str2:number = <number> <any> str   //str、str2 是 string 类型
var str3:number = str as unknown as number   //str、str3 是 string 类型
console.log(str2,str3) //1 1

类型推断

当类型没有给出时,TypeScript 编译器利用类型推断来推断类型。
如果由于缺乏声明而不能推断出类型,那么它的类型被视作默认的动态 any 类型

var num = 2;    // 类型推断为 number
console.log("num 变量的值为 "+num); 
num = "12";    // 编译错误
console.log(num);

变量作用域

变量作用域指定了变量定义的位置。
程序中变量的可用性由变量作用域决定。

TypeScript 有以下几种作用域:

  • 全局作用域:全局变量定义在程序结构的外部,它可以在你代码的任何位置使用。
  • 类作用域:这个变量也可以称为 字段。类变量声明在一个类里头,但在类的方法外面。 该变量可以通过类的对象来访问。类变量也可以是静态的,静态的变量可以通过类名直接访问。
  • 局部作用域:局部变量,局部变量只能在声明它的一个代码块(如:方法)中使用。
var global_num = 12          // 全局变量
class Numbers { 
   num_val = 13;             // 实例变量
   static sval = 10;         // 静态变量
   
   storeNum():void { 
      var local_num = 14;    // 局部变量
   } 
} 
console.log("全局变量为: "+global_num)  
console.log(Numbers.sval)   // 静态变量
var obj = new Numbers(); 
console.log("实例变量: "+obj.num_val) 

TypeScript 函数

函数是一组一起执行一个任务的语句。
您可以把代码划分到不同的函数中。如何划分代码到不同的函数中是由您来决定的,但在逻辑上,划分通常是根据每个函数执行一个特定的任务来进行的。
函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的实际主体

// 普通函数
function 函数名(形参1: 类型=默认值, 形参2:类型=默认值,...): 返回值类型 { }
// 声明式实际写法:
function add1(num1: number, num2: number): number {
  return num1 + num2
}

// 箭头函数
const 函数名(形参1: 类型=默认值, 形参2:类型=默认值, ...):返回值类型 => { }
const add2 = (a: number =100, b: number = 100): number =>{
   return a + b
 }

// 这里的 add1 和 add2 的参数类型和返回值一致,那么就可以统一定义一个函数类型
type Fn =(n1: number, n2: number) => number;
const add: Fn = (a, b) => {return a + b}

add1(1,1) // 2

函数返回值

有时,我们会希望函数将执行的结果返回到调用它的地方。
通过使用 return 语句就可以实现。
在使用 return 语句时,函数会停止执行,并返回指定的值
如果一个函数没有返回值,应该使用 void 类型

语法格式如下所示:

// 函数定义
function greet():string { // 返回一个字符串
    return "Hello World" 
} 
 
function caller() { 
    var msg = greet() // 调用 greet() 函数 
    console.log(msg) 
} 
    
// 如果什么都不写,此时,add 函数的返回值类型为: void
const add = () => {}
// 如果return之后什么都不写,此时,add 函数的返回值类型为: void
const add1 = () => { return }
const add2 = (): void => {
  // 此处,返回的 undefined 是 JS 中的一个值
  return undefined
}
// 这种写法是明确指定函数返回值类型为 void,与上面不指定返回值类型相同
const add3 = (): void => {}
    
caller()

带参数函数

在调用函数时,您可以向其传递值,这些值被称为参数。
这些参数可以在函数中使用。
您可以向函数发送多个参数,每个参数使用逗号 , 分隔:
语法格式如下所示:

function add(x: number, y: number): number {
    return x + y; 
  } 
console.log(add(1,2)) //3
    
// 可选参数(lastName?: string) 在 TypeScript 函数里,如果我们定义了参数,则我们必须传入这些参数,除非将这些参数设置为可选,可选参数使用问号标识 ?
// 可选参数必须跟在必需参数后面。 如果上例我们想让 firstName 是可选的,lastName 必选,那么就要调整它们的位置,把 firstName 放在后面。
// 果都是可选参数就没关系
function buildName(firstName: string, lastName?: string) {
    return firstName + " " + lastName;
}
 
function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}
 
let result1 = buildName("Bob");  // 正确
let result2 = buildName("Bob", "Adams", "Sr.");  // 错误,参数太多了
let result3 = buildName("Bob", "Adams");  // 正确

    
// 默认参数 (rate:number = 0.50) 如果不传入该参数的值,则使用默认参数
function calculate_discount(price:number,rate:number = 0.50) { 
    var discount = price * rate; 
    console.log("计算结果: ",discount); 
} 
calculate_discount(1000) //500
calculate_discount(1000,0.30) //300
    
    
// 剩余参数 不知道要向函数传入多少个参数,这时候我们就可以使用剩余参数来定义。
// 剩余参数语法允许我们将一个不确定数量的参数作为一个数组传入.
function addNumbers(...nums:number[]) {  
    var i;   
    var sum:number = 0; 
    
    for(i = 0;i<nums.length;i++) { 
       sum = sum + nums[i]; 
    } 
    console.log("和为:",sum) 
 } 
 addNumbers(1,2,3) 
 addNumbers(10,10,10,10,10)

                                 
//匿名函数
// 没有函数名的函数。匿名函数在程序运行时动态声明,除了没有函数名外,其他的与标准函数一样。可以将匿名函数赋值给一个变量,这种表达式就成为函数表达式。
var msg = function() { 
    return "hello world";  
} 
console.log(msg())        

TypeScript Number

Number 对象方法

 //toExponential() 把对象的值转换为指数计数法
let num1 = 1225.30 
let val = num1.toExponential(); 
console.log(val) // 输出: 1.2253e+3


// toFixed() 把数字转换为字符串,并对小数点指定位数。
let num3 = 177.234 
console.log("num3.toFixed() 为 "+num3.toFixed())    // 输出:177
console.log("num3.toFixed(2) 为 "+num3.toFixed(2))  // 输出:177.23
console.log("num3.toFixed(6) 为 "+num3.toFixed(6))  // 输出:177.234000

// toLocaleString() 把数字转换为字符串,使用本地数字格式顺序。
let num = new Number(177.1234); 
console.log( num.toLocaleString());  // 输出:177.1234


// toPrecision() 把数字格式化为指定的长度
let num5 = new Number(7.123456); 
console.log(num.toPrecision());  // 输出:7.123456 
console.log(num.toPrecision(1)); // 输出:7
console.log(num.toPrecision(2)); // 输出:7.1


// toString() 把数字转换为字符串,使用指定的基数。数字的基数是 2 ~ 36 之间的整数。若省略该参数,则使用基数 10。
var num6 = new Number(10); 
console.log(num.toString());  // 输出10进制:10
console.log(num.toString(2)); // 输出2进制:1010
console.log(num.toString(8)); // 输出8进制:12


// valueOf() 返回一个 Number 对象的原始数字值。
var num7 = new Number(10); 
console.log(num.valueOf()); // 输出:10
                               

TypeScript String

String 方法

// charAt() 返回在指定位置的字符。
var str = new String("RUNOOB");
console.log("str.charAt(0) 为:" + str.charAt(0)); // R
console.log("str.charAt(1) 为:" + str.charAt(1)); // U
console.log("str.charAt(2) 为:" + str.charAt(2)); // N
console.log("str.charAt(3) 为:" + str.charAt(3)); // O
console.log("str.charAt(4) 为:" + str.charAt(4)); // O
console.log("str.charAt(5) 为:" + str.charAt(5)); // B

// indexOf() 返回某个指定的字符串值在字符串中首次出现的位置。
var str1 = new String("RUNOOB");
var index = str1.indexOf("OO");
console.log("查找的字符串位置 :" + index); // 3

// slice() 提取字符串的片断,并在新的字符串中返回被提取的部分
var str2 = new String("RUNOOB");
console.log("str.slice(0,2) 返回 :" + str2.slice(0, 2)); // RU

// 	split() 把字符串分割为子字符串数组。
var str3 = "Apples are round, and apples are juicy.";
var splitted = str3.split(" ", 3);
console.log(splitted); // [ 'Apples', 'are', 'round,' ]

// substring() 提取字符串中介于两个指定下标之间的字符。
var str4 = "RUNOOB GOOGLE TAOBAO FACEBOOK";
console.log("(1,2): " + str4.substring(1, 2)); // U
console.log("(0,10): " + str4.substring(0, 10)); // RUNOOB GOO
console.log("(5): " + str4.substring(5)); // B GOOGLE TAOBAO FACEBOOK

// 	toLowerCase() 转换为小写
//  toUpperCase() 转换为大写
var str5 = "Runoob Google";
console.log(str.toLowerCase()); // runoob google
console.log(str.toUpperCase()); // RUNOOB GOOGLE

TypeScript Array(数组)

数组的一些方法

let numList:number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// every() 检查数组的每个元素是否都满足条件 
// 全部满足返回 true 否则返回 false
let eve = numList.every((num) => num >0)  // true
let eve1 = numList.every((num) => num >5)  // false

// some() 检查数组中是否有至少一个元素满足条件
// 满足返回 true 否则返回 false
let som = numList.some((num) => num >5)  // true

//filter() 返回一个新数组,包含所有满足条件的元素
let filt= numList.filter((num) => num >5)  // [6, 7, 8, 9, 10]

//forEach() 遍历数组,对每个元素执行一次回调函数
numList.forEach((num) => console.log(num))  // 1 2 3 4 5 6 7 8 9 10

//indexOf() 返回数组中第一个满足条件的元素的索引,如果没有找到则返回 -1
let ind = numList.indexOf(5)  // 4

//find() 返回数组中第一个满足条件的元素,如果没有找到则返回 undefined
let fin = numList.find((num) => num >5)  // 6


// join() 将数组的所有元素连接成一个字符串,并返回该字符串
let join = numList.join('-')  // "1-2-3-4-5-6-7-8-9-10"

//map() 返回一个新数组,包含对原数组每个元素执行回调函数后的结果
let map = numList.map((num) => num * 2)  // [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

//reduce() 对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。
let reduce = numList.reduce((acc, num) => acc + num, 0)  // 55

// reverse() 反转数组中元素的顺序
let rev = numList.reverse()  // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

// slice() 返回一个新数组,包含原数组中指定范围的元素
let slice = numList.slice(2, 5)  // [3, 4, 5]

// sort() 对数组中的元素进行排序,并返回数组
let sort = numList.sort((a, b) => a - b)  // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let sort1 = numList.sort((a, b) => b - a)  // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

// splice() 通过删除现有元素和/或添加新元素来更改一个数组的内容
let numList1:number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let splice = numList1.splice(2, 3, 11, 12, 13) 

console.log('numList1',numList1,'splice',splice); // numList1 [1, 2, 11, 12, 13, 6, 7, 8, 9, 10] splice [3, 4, 5]

TypeScript 联合类型

联合类型(Union Types)可以通过管道(|)将变量设置多种类型,赋值时可以根据设置的类型来赋值。

注意:只能赋值指定的类型,如果赋值其它类型就会报错。

// 声明一个联合类型
var val: string | number;
val = 12;
console.log("数字为 " + val); // 数字为 12
val = "Runoob";
console.log("字符串为 " + val); // 字符串为 Runoob

    
// 作为函数参数使用
const disp = (name: string | string[]) => {
  if (typeof name == "string") {
    console.log(name);
  } else {
    let i;
    for (i = 0; i < name.length; i++) {
      console.log(name[i]);
    }
  }
};

disp("Runoob"); // 输出字符串 Runoob
disp(["Runoob", "Google", "Taobao", "Facebook"]); //输出数组中的元素 Runoob Google Taobao Facebook

                                
// 联合类型数组
let arr: number[] | string[];
let i: number;
arr = [1, 2, 4, 5];
console.log("数字数组");
for (i = 0; i < arr.length; i++) {
  console.log(arr[i]); //1 2 3 4 5
}

arr = ["Runoob", "Google", "Taobao"];
console.log("字符串数组");
for (i = 0; i < arr.length; i++) {
  console.log(arr[i]); // Runoob Google Taobao
}

TypeScript 类型别名

type NewType = string | number

let a: NewType = 1
let b: NewType = '1'                         

TypeScript 接口 (interface)

接口是一系列抽象方法的声明,是一些方法特征的集合,这些方法都应该是抽象的,需要由具体的类去实现,然后第三方就可以通过这组抽象方法调用,让具体的类执行具体的方法。

// 定义接口
interface IPerson {
  firstName: string;
  lastName: string;
  sayHi: () => string;
}

// Customer 对象
var customer: IPerson = {
  firstName: "Tom",
  lastName: "Hanks",
  sayHi: (): string => {
    return "Hi there";
  },
};
console.log("Customer 对象 ");
console.log(customer.firstName); // Tom
console.log(customer.lastName); // Hanks
console.log(customer.sayHi()); // Hi there

// Employee 对象
var employee: IPerson = {
  firstName: "Jim",
  lastName: "Blakes",
  sayHi: (): string => {
    return "Hello!!!";
  },
};
console.log("Employee  对象 ");
console.log(employee.firstName); // Jim
console.log(employee.lastName); // Blakes

// 联合类型和接口
interface RunOptions {
  program: string;
  commandline: string[] | string | (() => string);
}

// commandline 是字符串
var options: RunOptions = { program: "test1", commandline: "Hello" };
console.log(options.commandline); // Hello

// commandline 是字符串数组
options = { program: "test1", commandline: ["Hello", "World"] };
console.log(options.commandline[0]); // Hello
console.log(options.commandline[1]); // World

// commandline 是一个函数表达式
options = {
  program: "test1",
  commandline: () => {
    return "**Hello World**";
  },
};

var fn: any = options.commandline;
console.log(fn()); // **Hello World**

// 接口和数组
// 定义了一个名为StringArray的接口,其中包含一个名为index的数字类型的属性,以及一个名为string的string类型的属性,这个接口表示一个数组,其中每个元素都是一个字符串。在这个接口中,我们使用了索引签名,这意味着我们可以使用任何数字作为键来访问数组中的元素
interface StringArray {
  [index: number]: string;
}

// 类型一致,正确
var list2: StringArray = ["Google", "Runoob", "Taobao"];
// 错误元素 1 不是 string 类型
//  var list2:StringArray = ["Runoob",1,"Taobao"]

// 接口继承接口
interface Shape {
  color: string;
}

interface PenStroke {
  penWidth: number;
}

// 接口继承接口,把多个接口合并为一个接口
interface Square extends Shape, PenStroke {
  sideLength: number;
}

let square: Square = {
  color: "blue",
  penWidth: 100,
  sideLength: 10,
};
console.log(square.color); // blue

// var square = <Square>{}
// square.color = "blue";
// square.sideLength = 10;
// square.penWidth = 5.0;
// console.log(square.color); // blue

TypeScript 类

TypeScript 是面向对象的 JavaScript。
类描述了所创建的对象共同的属性和方法。
TypeScript 支持面向对象的所有特性,比如 类、接口等。

TypeScript 类定义方式如下

class Car {
  // 字段
  engine: string;

  // 构造函数
  constructor(engine: string) {
    this.engine = engine;
  }

  // 方法
  disp(): void {
    console.log("函数中显示发动机型号  :   " + this.engine);
  }
}

// 创建一个对象
var obj = new Car("XXSY1");

// 访问字段
console.log("读取发动机型号 :  " + obj.engine);

// 访问方法
obj.disp();

// 类的继承
// TypeScript 支持继承类,即我们可以在创建类的时候继承一个已存在的类,这个已存在的类称为父类,继承它的类称为子类。
// 类继承使用关键字 extends,子类除了不能继承父类的私有成员(方法和属性)和构造函数,其他的都可以继承。
// TypeScript 一次只能继承一个类,不支持继承多个类,但 TypeScript 支持多重继承(A 继承 B,B 继承 C)。
class Shape {
  Area: number;

  constructor(a: number) {
    this.Area = a;
  }
}

class Circle extends Shape {
  disp(): void {
    console.log("圆的面积:  " + this.Area);
  }
}

var obj1 = new Circle(223);
obj1.disp(); // 输出结果为:圆的面积:  223

// 类的静态部分 (static 关键字)
// 静态部分是类的一部分,而不是类的实例的一部分。
// 静态属性用于定义类的属性,它们不会被实例继承。
// 静态方法用于定义类的函数,它们也不能被实例继承。
class StaticMem {
  static num: number;
  static disp(): void {
    console.log("num 值为 " + StaticMem.num);
  }
}
StaticMem.num = 12; // 设置值
StaticMem.disp(); // 调用静态方法

// instanceof 运算符
// instanceof 运算符用于判断对象是否是指定的类型,如果是返回 true,否则返回 false。
class Person {}
var obj3 = new Person();
var isPerson = obj3 instanceof Person;
console.log("obj3 对象是 Person 类实例化来的吗? " + isPerson); // obj 对象是 Person 类实例化来的吗? true

// 访问控制修饰符
// 可以使用访问控制符来保护对类、变量、方法和构造方法的访问。TypeScript 支持 3 种不同的访问权限。
// public(默认) : 公有,可以在任何地方被访问。
// protected : 受保护,可以被其自身以及其子类访问。
// private : 私有,只能被其定义所在的类访问。
class Encapsulate {
  str1: string = "hello";
  private str2: string = "world";
}

var obj4 = new Encapsulate();
console.log(obj4.str1); // 可访问
console.log(obj4.str2); // 编译错误, str2 是私有的

// 类的接口
// 类可以实现接口,使用关键字 implements,并将 interest 字段作为类的属性使用。
interface ILoan {
  interest: number;
}

class AgriLoan implements ILoan {
  interest: number;
  rebate: number;

  constructor(interest: number, rebate: number) {
    this.interest = interest;
    this.rebate = rebate;
  }
}

var obj5 = new AgriLoan(10, 1);
console.log("利润为 : " + obj5.interest + ",抽成为 : " + obj5.rebate);  // 利润为 : 10,抽成为 : 1

TypeScript 泛型

泛型(Generics)是一种编程语言特性,允许在定义函数、类、接口等时使用占位符来表示类型,而不是具体的类型。

泛型是一种在编写可重用、灵活且类型安全的代码时非常有用的功能。

使用泛型的主要目的是为了处理不特定类型的数据,使得代码可以适用于多种数据类型而不失去类型检查。

泛型的优势包括:

  • 代码重用:  可以编写与特定类型无关的通用代码,提高代码的复用性。
  • 类型安全:  在编译时进行类型检查,避免在运行时出现类型错误。
  • 抽象性:  允许编写更抽象和通用的代码,适应不同的数据类型和数据结构。
// 泛型标识符
// 比如常见的 T(表示 Type)、K、V 等,但实际上你可以使用任何标识符。

// T: 代表 "Type",是最常见的泛型类型参数名。
function identity<T>(arg: T): T {
  return arg;
}
// K, V: 用于表示键(Key)和值(Value)的泛型类型参数。
interface KeyValuePair<K, V> {
  key: K;
  value: V;
}

// 泛型函数(Generic Functions)
// 泛型函数可以在定义时指定一个或多个类型参数,然后在调用时传入具体的类型参数。例如:
function identity1<T>(arg: T): T {
  return arg;
}

// 使用泛型函数
let result = identity1<string>("Hello");
console.log(result); // 输出: Hello

let numberResult = identity1<number>(42);
console.log(numberResult); // 输出: 42
// 解析: 以上例子中,identity 是一个泛型函数,使用 <T> 表示泛型类型。它接受一个参数 arg 和返回值都是泛型类型 T。在使用时,可以通过尖括号 <> 明确指定泛型类型。第一个调用指定了 string 类型,第二个调用指定了 number 类型

// 泛型接口(Generic Interfaces)
// 泛型接口可以在定义时指定一个或多个类型参数,然后在实现接口时传入具体的类型参数。例如:
// 基本语法
interface Pair<T, U> {
  first: T;
  second: U;
}

// 使用泛型接口
let pair: Pair<string, number> = { first: "hello", second: 42 };
console.log(pair); // 输出: { first: 'hello', second: 42 }

// 泛型类(Generic Classes)
// 泛型类可以在定义时指定一个或多个类型参数,然后在实例化类时传入具体的类型参数。例如:
// 基本语法
class Box<T> {
  private value: T;

  constructor(value: T) {
    this.value = value;
  }

  getValue(): T {
    return this.value;
  }
}

// 使用泛型类
let stringBox = new Box<string>("TypeScript");
console.log(stringBox.getValue()); // 输出: TypeScript

// 泛型约束(Generic Constraints)
// 泛型约束用于限制泛型类型参数必须满足特定的条件。例如,可以约束泛型类型参数必须实现某个接口,或者必须具有某个属性。例如:
interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): void  {
  console.log(arg.length); // 现在编译器知道 arg 有 length 属性
}

// 正确的使用
loggingIdentity("hello"); // 输出: 5

// 错误的使用,因为数字没有 length 属性
loggingIdentity(42); // 错误



//  泛型与默认值
// 基本语法
function defaultValue<T = string>(arg: T): T {
  return arg;
}

// 使用带默认值的泛型函数
let result1 = defaultValue("hello"); // 推断为 string 类型
let result2 = defaultValue(42);      // 推断为 number 类型