TypeScript入门到精通——TypeScript类型系统基础——类
类
JavaScript 是一门面向对象的编程语言,它允许通过对象来建模和解决实际问题。同时,JavaScript 也支持基于原型链的对象继承机制。虽然大多数的面向对象编程语言都支持类,但是 JavaScript 语言在很长一段时间都没有支持它。在 JavaScript 程序中,需要使用函数来实现类的功能。
在 ECMAScript 2015 规范中正式地定义了类。同时,TypeScript 语言也对类进行了全面的支持。
一、类的定义
虽然 JavaScript 语言支持了类,但其本质上仍然是函数,类是一种语法糖。TypeScript 语言对 JavaScript 中的类进行了罗占,为其添加了类型支持,如实现接口、泛型类等。
定义一个类需要使用 class 关键字。类型于函数定义,类的定义也有以下两种方式:
-
- 类声明
- 类表达式
1.1、类声明
类声明能够创建一个类,类声明的语法如下所示:
1 2 3 4 | class ClassName { // ... } |
在该语法中,class 是关键字;ClassName 表示类的名字。在类声明中的类名是必选的。按照惯例,类名的首字母应该大写。示例如下:
1 2 3 4 5 | class Circle{ radius: number; } const c = new Circle(); |
与函数声明不同的是,类声明不会被提升,就是必须先声明后,再使用。示例如下:
1 2 3 4 5 6 7 8 9 | const c0 = new Circle(); //错误 class Circle{ radius: number; } const c1 = new Circle(); //正确 |
1.2、类表达式
类表达式是另一种定义类的方式,它的语法如下所示:
1 2 3 | const Name = class ClassName { // ... }; |
在该语法中,class 是关键字;Name 表示引用了该类的变量名;ClassName 表示类的名字。在类表达式中,类名 ClassName 是可选的。
例如,下例中使用类表达式定义了一个匿名类,同时使用常量 Circle 引用了该匿名类:
1 2 3 | const Circle = class { radius: number; }; |
如果在类表达式中定义了类型,则该类型只能够在类内部使用,在类外不允许引用该类名。
1 2 3 4 5 6 | const A = class B { name = B.name; }; const b = new B(); // error |
二、成员变量
TypeScript 是一种基于 JavaScript 的强类型或静态类型语言。成员变量,也称为实例变量或属性,是定义在类中的变量,用于保存对象的状态。
在 TypeScript 中,成员变量可以在类的构造函数中初始化,也可以在声明时直接赋值。这些变量属于类的实例,所以每个实例都会有一份自己的副本。
下面是一个简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Animal { // 成员变量 name: string; age: number; constructor(name: string, age: number) { this .name = name; this .age = age; } } let cat = new Animal( 'Tom' , 5); console.log(cat.name); // 输出 'Tom' console.log(cat.age); // 输出 5 |
TypeScript 的成员变量可以有访问修饰符,比如 public(默认)、private 和 protected。这些修饰符决定了成员变量的可见性,也就是它们在类的外部是否可以被访问或修改。
1 2 3 4 5 6 7 8 9 | class Animal { private name: string; // 私有成员变量 public age: number; // 公有成员变量 constructor(name: string, age: number) { this .name = name; this .age = age; } } |
在这个例子中,name 是私有的,所以在类的外部无法访问或修改它。而 age 是公有的,所以在类的外部可以访问和修改它。
三、成员函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Person { name: string; age: number; constructor(name: string, age: number) { this .name = name; this .age = age; } sayHello(): void { console.log(Hello, my name is ${ this .name} and I am ${ this .age} years old.); } } const person = new Person( 'Alice' , 25); person.sayHello(); // 输出:Hello, my name is Alice and I am 25 years old. |
在上面的例子中,Person 类有两个成员变量 name 和 age,以及一个成员函数 sayHello。sayHello 函数使用 console.log 输出一个问候语,其中包含当前实例的 name 和 age。 注意,在这个例子中使用了 this 关键字来引用当前类的实例。this 关键字可以用来访问类的成员变量和其他成员函数。 类成员函数还可以接收参数,如下所示:
1 2 3 4 5 6 7 8 | class Calculator { add(a: number, b: number): number { return a + b; } } const calculator = new Calculator(); console.log(calculator.add(2, 3)); // 输出:5 |
在上面的例子中,Calculator 类有一个接收两个数字参数并返回它们之和的成员函数 add。
四、成员存取器
TypeScript 中的类成员存取器(getter 和 setter)是一种特殊的类成员函数,用于访问和修改类的成员变量。它们提供了一种更安全和可控的方式来处理类的属性。
getter 是一个只读函数,用于获取成员变量的值。它的名称与成员变量相同,但前面加上 get 关键字。getter 函数不接收任何参数,返回成员变量的值。
setter 是一个写函数,用于设置成员变量的值。它的名称与成员变量相同,但前面加上 set 关键字。setter 函数接收一个参数,该参数是要设置的新值。
下面是一个使用 getter 和 setter 的 TypeScript 类示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | class Person { private _name: string; private _age: number; constructor(name: string, age: number) { this ._name = name; this ._age = age; } get name(): string { return this ._name; } set name(value: string) { if (value.length > 0) { this ._name = value; } else { console.log( "Name cannot be empty." ); } } get age(): number { return this ._age; } set age(value: number) { if (value > 0) { this ._age = value; } else { console.log( "Age cannot be negative." ); } } } |
在这个示例中,Person类有两个私有成员变量 _name 和 _age,以及对应的 getter 和 setter。
getter 和 setter 允许我们更好地控制对成员变量的访问和修改。在这个例子中,setter 函数对输入值进行了检查,确保名字不为空,年龄不为负。如果不满足这些条件,setter 将不会修改成员变量,而是输出一条错误消息。这是一种数据验证的有效方式。 使用getter和setter的另一个好处是,它们可以让我们在未来更改类的内部实现时,保持对外部代码的兼容性。
例如,如果我们以后决定不再直接存储_name和_age,而是将它们存储在数据库或远程服务器上,我们只需要修改getter和setter,而不需要修改所有使用这些变量的代码。getter和setter是TypeScript(和许多其他面向对象语言)中非常有用的特性,可以提高代码的可读性、安全性和可维护性。
五、索引成员
类的索引成员会在类的类型中引入索引签名。
索引签名包含两种:
-
- 字符串索引: 使用字符串作为键来访问对象的属性。在类中,你可以使用字符串索引来访问类的属性。
123456789
class
MyClass {
private
myProperty: string;
constructor() {
this
.myProperty =
"Hello, World!"
;
}
}
let
myObject =
new
MyClass();
console.log(myObject[
"myProperty"
]);
// 输出 "Hello, World!"
在上面的例子中,我们使用字符串索引
"myProperty"
来访问MyClass
实例的属性。 - 数值索引: 使用数值作为键来访问对象的属性。在类中,你可以使用数值索引来访问类的属性或元素。
123456789
class
MyArray {
private
myElements: number[];
constructor(elements: number[]) {
this
.myElements = elements;
}
}
let
myArray =
new
MyArray([1, 2, 3, 4, 5]);
console.log(myArray[2]);
// 输出 3
在上面的例子中,我们使用数值索引
2
来访问MyArray
实例的元素
- 字符串索引: 使用字符串作为键来访问对象的属性。在类中,你可以使用字符串索引来访问类的属性。
六、成员可访问性
在 TypeScript 中,类的成员可以通过访问修饰符来控制其可访问性。访问修饰符有以下四种:
-
- public:公共访问修饰符,表示类的成员可以从任何地方访问。
- protected:受保护的访问修饰符,表示类的成员只能在类内部或派生类中访问。
- private:私有访问修饰符,表示类的成员只能在类内部访问。
- #private(私有字段):这是一种特殊的私有访问修饰符,用于声明私有字段。
6.1、public
例如,假设有一个 Person
类,它有一个 name
属性和一个 greet
方法:
1 2 3 4 5 6 7 | class Person { public name: string; public greet(): string { return `Hello, my name is ${ this .name}`; } } |
可以直接创建 Person
的实例并访问其 name
属性和 greet
方法:
1 2 3 | const person = new Person(); person.name = "Alice" ; console.log(person.greet()); // 输出 "Hello, my name is Alice" |
6.2、protected
例如,假设有一个 Animal 类,它有一个 protected name 属性和一个 protected speak 方法:
1 2 3 4 5 6 7 | class Animal { protected name: string; protected speak(): string { return `My name is ${ this .name}`; } } |
你可以在派生类中访问 Animal
的受保护成员:
1 2 3 4 5 6 | class Dog extends Animal { bark(): void { console.log( this .name); // 可以访问受保护的 name 属性 console.log( this .speak()); // 可以访问受保护的 speak 方法 } } |
但在类的外部无法直接访问受保护的成员:
1 2 3 | const dog = new Dog(); // 错误!Dog 类不能直接实例化,只能通过继承 Animal 类的方式创建派生类对象。 console.log(dog.name); // 错误!name 属性是受保护的,不能从外部直接访问。 console.log(dog.speak()); // 错误!speak 方法是受保护的,不能从外部直接访问。 |
6.3、private
例如,假设有一个 BankAccount
类,它有一个 private balance
属性和一个 deposit
方法:
1 2 | class BankAccount { private balance: number; // private 属性只能在 BankAccount 类内部直接访问,不能从外部或派生类中直接访问。 |
6.4、私有字段
例如,让我们以 BankAccount
类为例,添加一个私有的 #balance
字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class BankAccount { #balance: number; // 私有字段 constructor(initialBalance: number) { this . #balance = initialBalance; // 在类内部访问和赋值私有字段 } deposit(amount: number) { this . #balance += amount; // 在类内部访问和操作私有字段 } get balance() { return this . #balance; // 通过 getter 访问私有字段(在外部不可直接访问) } } |
建一个 BankAccount
的实例,并使用其 deposit
方法以及通过其 balance
getter 来查看余额:
1 2 3 4 | const account = new BankAccount(1000); // 创建一个 BankAccount 实例 console.log(account.balance); // 输出 1000 account.deposit(500); // 存入 500 console.log(account.balance); // 输出 1500 |
但是,如果你尝试直接访问 #balance
字段(无论是从类的外部还是从派生类中),TypeScript 都会给出错误,因为它是私有的。例如:
1 | console.log(account. #balance); // 错误!无法直接从外部访问 #balance 字段 |
七、构造函数
在 TypeScript 中,类是一种用户自定义的数据类型,它允许您封装数据和相关操作。构造函数是类的一个特殊方法,用于初始化新创建的对象实例的状态。
下面是一个简单的示例,展示了如何在 TypeScript 中定义一个带有构造函数的类:
1 2 3 4 5 6 7 8 9 10 11 | class Person { constructor(public name: string, public age: number) {} greet() { console.log(`Hello, my name is ${ this .name} and I am ${ this .age} years old.`); } } // 创建一个 Person 对象实例 const person1 = new Person( "Alice" , 25); person1.greet(); // 输出: Hello, my name is Alice and I am 25 years old. |
在上面的示例中,Person
类有一个构造函数。当您使用 new
关键字创建 Person
类的对象实例时,构造函数会被调用,并传入 name
和 age
参数。这些参数被用来初始化新创建的对象实例的 name
和 age
属性。
注意,在 TypeScript 中,构造函数使用 constructor
关键字进行定义,并且它们总是在类定义的顶部。在构造函数中,您可以定义并初始化类的属性或执行其他必要的初始化操作。
构造函数可以有参数,并且参数可以带有类型注解。在上面的示例中,构造函数的参数具有类型注解 public name: string
和 public age: number
,这表示构造函数期望传入一个字符串和一个数字作为参数。通过使用类型注解,TypeScript 可以帮助您在编译时捕获类型错误。
八、参数成员
在 TypeScript 中,类的参数成员是指在类构造函数中定义的参数。这些参数被用来初始化类的属性或执行其他必要的初始化操作。
在类构造函数中,可以定义多个参数,每个参数都具有一个类型注解。这些类型注解用于指定参数的数据类型,以便在编译时进行类型检查。
下面是一个示例,展示了如何在 TypeScript 类中定义参数成员:
1 2 3 4 5 6 7 8 9 | class Person { constructor(public name: string, public age: number) { // 在构造函数内部可以执行其他初始化操作 } greet() { console.log(`Hello, my name is ${ this .name} and I am ${ this .age} years old.`); } } |
在上面的示例中,Person
类有一个构造函数,它接受两个参数:name
和age
。这些参数被用来初始化类的属性name
和age
。注意,在构造函数参数的右侧使用类型注解public name: string
和public age: number
,这表示这些参数应该传入一个字符串和一个数字。
在构造函数内部,您可以执行其他初始化操作,例如为属性赋予默认值或进行其他必要的设置。在上面的示例中,构造函数没有进行其他操作,但您可以根据需要添加其他逻辑。
当您创建一个类的实例时,需要传递相应的参数给构造函数。例如:
1 2 | const person1 = new Person( "Alice" , 25); person1.greet(); // 输出: Hello, my name is Alice and I am 25 years old. |
在上面的代码中,我们使用new
关键字创建了一个Person
类的对象实例,并传递了两个参数给构造函数:"Alice"
和25
。这些参数被用来初始化person1
对象的name
和age
属性。然后,我们调用greet方
法来输出问候语。
九、继承
9.1、重写基类成员
在 TypeScript 中,派生类可以重写基类的成员。例如,如果我们有一个基类Animal
和一个派生类Dog
,Dog
可以重写Animal
的speak
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Animal { speak(volume: number) { console.log(`The animal speaks with volume ${volume}`); } } class Dog extends Animal { speak(volume: number) { // 重写基类的 speak 方法 console.log(`The dog barks with volume ${volume}`); } } let dog = new Dog(); dog.speak(5); // 输出: "The dog barks with volume 5" |
9.2、派生类实例化
当我们创建一个派生类的实例时,这个实例会自动继承基类的属性和方法。在上述的例子中,我们可以创建一个Dog
类的实例并调用speak
方法:
1 2 | let dog = new Dog(); dog.speak(5); // 输出: "The dog barks with volume 5" |
9.3、单继承
在 TypeScript 中,一个类只能继承另一个类。这就是单继承的含义。
例如,我们可以创建一个Mammal
类,它是Animal
的子类:
1 2 3 4 5 | class Mammal extends Animal { feed() { console.log( "The mammal is being fed" ); } } |
Mammal
继承了Animal
的所有属性和方法,包括speak
。
9.4、接口继承类
在 TypeScript 中,接口可以继承类或另一个接口的成员。这允许我们创建更具体的接口。例如,我们可以创建一个DogService
接口,它继承自AnimalService
接口:
1 2 3 4 5 6 7 | interface AnimalService { getAnimal(): Animal; } interface DogService extends AnimalService { getDog(): Dog; // 扩展了 AnimalService 接口的 getAnimal 方法 } |
DogService
接口继承了AnimalService
接口的所有成员(包括getAnimal
方法),并添加了一个新的方法getDog
。
十、实现接口
在TypeScript中,类可以实现接口。实现接口可以让一个类拥有接口所定义的方法和属性。这有助于实现代码的解耦和增强可读性。
要实现一个接口,类需要包含与接口中定义的方法和属性相同的签名。下面是一个示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | //定义一个接口 Person interface Person { name: string; //一个字符串类型的属性。 age: number; // 一个数字类型的属性。 greet: (message: string) => void; //一个函数,接收一个字符串参数 message,没有返回值(返回类型为 void)。 } //定义一个类 Emplyee //这个类实现了上述定义的 Person 接口。这意味着它必须包含与 Persion 接口中定义的所有属性和方法相同的签名。 //在 TypeScript 中,implements 是一个关键字,用于实现接口。它表示一个类将实现一个接口的所有方法和属性。在这个例子中,Employee 类实现了 Person 接口,这意味着 Employee 类必须包含与 Person 接口中定义的所有属性和方法相同的签名。 class Employee implements Person { name: string; //一个字符串类型的属性。 age: number; //一个数字类型的属性。 //类的构造函数,接收两个参数,一个字符串 name 和一个数字 age。这些参数用于初始化上述的两个属性。 constructor(name: string, age: number) { this .name = name; this .age = age; } //实现接口的 greet 方法。这个方法接收一个字符串参数 message 打印。 greet(message: string) { console.log(`${message}, ${ this .name}!`); } } |
现在,我们可以创建一个Employee
对象并调用其方法:
1 2 | const employee = new Employee( "John" , 30); employee.greet( "How are you?" ); // 输出: "How are you, John!" |
通过实现接口,我们可以确保类具有所需的规范,并且可以使用接口中定义的方法和属性来扩展类的功能。这使得代码更加灵活和可维护。在 TypeScript 中,接口提供了一种方法来定义一个类必须具有的结构和行为,以及可以在多个类之间共享的通用模板。
十一、静态成员
11.1、静态成员可访问性
在 TypeScript 中,类可以包含静态成员。静态成员是属于类的成员,而不是类的实例的成员。这意味着你可以在不创建类的实例的情况下访问静态成员。静态成员在类中定义,并且在类的任何实例上都是可访问的。
1 2 3 4 5 6 7 8 9 10 11 12 13 | class MyClass { static staticProperty: string = 'Hello, World!' ; static staticMethod() { console.log( 'This is a static method.' ); } } // 访问静态属性 console.log(MyClass.staticProperty); // 输出:Hello, World! // 调用静态方法 MyClass.staticMethod(); // 输出:This is a static method. |
关于静态成员的访问性,TypeScript支持使用public
、protected
和private
修饰符来指定静态成员的可见性。这些修饰符的使用方式与实例成员相同。例如,你可以将静态属性或方法声明为public
,使其在类的任何地方都可以访问;或者使用private
修饰符将其限制在类的内部。
11.2、继承静态成员
继承是面向对象编程的一个重要概念,TypeScript 也支持类的继承。然而,与实例成员不同,静态成员不能被继承。这意味着子类不能继承父类的静态成员。每个类都有自己独立的静态成员。
虽然静态成员不能被继承,但子类可以通过原型链访问父类的静态成员。当你在子类中访问一个静态成员时,如果该成员在父类中不存在,那么将会在子类的原型链上查找该成员。这使得你可以在子类中重写父类的静态成员,就像你可以重写实例方法一样。
下面是一个示例,展示了如何在 TypeScript 中使用静态成员的继承:
1 2 3 4 5 6 7 8 9 10 11 12 13 | class ParentClass { static staticProperty: string = 'Hello from Parent' ; } class ChildClass extends ParentClass { static staticProperty: string = 'Hello from Child' ; } // 访问父类的静态属性 console.log(ParentClass.staticProperty); // 输出:Hello from Parent // 访问子类的静态属性(会覆盖父类的静态属性) console.log(ChildClass.staticProperty); // 输出:Hello from Child |
在上面的示例中,ParentClass
和ChildClass
都定义了一个名为staticProperty
的静态属性。由于ChildClass
继承自ParentClass
,所以ChildClass
可以访问ParentClass
的静态属性。同时,ChildClass
也定义了自己的静态属性,并且通过使用static
关键字,确保了它不会被继承。
十二、抽象类和抽象成员
在 TypeScript 中,抽象类(Abstract Class)和抽象成员(Abstract Members)是用于实现面向对象编程的重要特性。它们提供了一种方式来定义不能直接实例化的类,而是用作其他类的基类。
12.1、抽象类
抽象类是一种不能直接实例化的类,它用于定义抽象成员。抽象类只能被继承,并且派生类必须实现所有的抽象成员。
在 TypeScript 中,使用abstract
关键字来声明抽象类。下面是一个简单的示例:
1 2 3 4 | abstract class AbstractClass { abstract member1(): void; abstract member2(): string; } |
在上面的示例中,AbstractClass
是一个抽象类,它定义了两个抽象成员member1
和member2
。这两个成员都被声明为抽象成员,因此派生类必须提供它们的具体实现。
12.2、抽象成员
抽象成员是定义在抽象类中的方法或属性,它没有具体的实现。派生类必须提供抽象成员的具体实现。
在 TypeScript 中,使用abstract
关键字来声明抽象成员。下面是一个简单的示例:
1 2 3 4 | abstract class AbstractClass { abstract member1(): void; // 抽象方法 abstract member2(): string; // 抽象属性 } |
在上面的示例中,member1
和 member2
都被声明为抽象成员。这意味着任何继承自AbstractClass
的类都必须提供它们的具体实现。
派生类可以通过实现抽象成员来继承抽象类的行为。下面是一个使用抽象类和抽象成员的示例:
1 2 3 4 5 6 7 8 9 | class DerivedClass extends AbstractClass { member1(): void { // 实现抽象方法 member1 的具体逻辑 } member2(): string { // 实现抽象属性 member2 的具体逻辑并返回一个字符串 return "Hello, world!" ; } } |
在上面的示例中,DerivedClass
继承自AbstractClass
并实现了所有的抽象成员。现在,我们可以创建DerivedClass
的实例并调用它的方法:
1 2 3 | const obj = new DerivedClass(); // 创建派生类的实例 obj.member1(); // 调用派生类实现的方法 console.log(obj.member2()); // 访问派生类实现的属性并打印结果:"Hello, world!" |
十三、this 类型
在 TypeScript 中,类的this
类型是指在该类中this
关键字的类型。在 TypeScript 中,this
关键字用于访问当前对象的属性和方法。
在类中,this
关键字的类型是该类的实例类型。这意味着,当你在类的方法中使用this
关键字时,它引用的类型是该类的实例类型。
下面是一个简单的示例,展示了如何在 TypeScript 类中使用this
类型:
1 2 3 4 5 6 7 8 9 10 | class MyClass { constructor( private name: string) {} sayHello() { console.log(`Hello, my name is ${ this .name}`); // this 指向 MyClass 的实例 } } let myObject = new MyClass( "Alice" ); myObject.sayHello(); // 输出 "Hello, my name is Alice" |
在上面的示例中,MyClass
是一个简单的类,有一个私有属性name
和一个公有方法sayHello
。在sayHello
方法中,this
关键字用于引用MyClass
的实例对象。在实例化MyClass
时,我们传递了一个字符串参数 "Alice" 给构造函数,从而设置了实例的name
属性。然后,我们调用sayHello
方法,它使用this.name
来访问和打印实例的name
属性。
在 TypeScript 中,你可以使用类型断言来明确指定this
的类型。例如,如果你想在某个方法中明确指定this
的类型是MyClass
的实例类型,你可以使用类型断言:
1 2 3 4 5 6 7 8 9 10 11 12 13 | class MyClass { constructor( private name: string) {} sayHello() { console.log(`Hello, my name is ${ this .name}`); // this 指向 MyClass 的实例 } getThisType() { // 使用类型断言明确指定 this 的类型为 MyClass 的实例类型 let thisType: MyClass = this ; return thisType; } } |
十四、类类型
在 TypeScript 中,一个类的类型是由其所有实例共享的。这意味着,如果你有一个类的实例,你可以使用该类的任何方法或访问其任何属性,而无需再次实例化该类。类类型是一种用于表示类的结构和行为的类型。
在 TypeScript 中,类类型是通过使用class
关键字来声明的。下面是一个简单的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class MyClass { constructor( private name: string) {} sayHello() { console.log(`Hello, my name is ${ this .name}`); } } // MyClass 的类类型 let myClassType: MyClass; // 创建一个 MyClass 的实例 let myObject = new MyClass( "Alice" ); // 可以使用类的实例方法 myObject.sayHello(); // 输出 "Hello, my name is Alice" // 可以使用类的实例属性(在这个例子中没有实例属性,但可以有) console.log(myObject.name); // 输出 "Alice" |
在上面的示例中,我们声明了一个名为MyClass
的类,它有一个私有属性name
和一个公有方法sayHello
。然后,我们声明了一个名为myClassType
的变量,该变量的类型是MyClass
。这意味着我们可以将MyClass
的实例赋值给myClassType
变量。然后,我们创建了一个MyClass
的实例,并将其赋值给myObject
变量。我们可以使用myObject
变量来调用sayHello
方法,并访问name
属性。
需要注意的是,在 TypeScript 中,类的构造函数必须使用new
关键字来调用。这是因为 TypeScript 使用构造函数来创建类的实例,并且使用new
关键字可以确保类的实例被正确地初始化。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具