TS:获取数组的元素类型

本文介绍如何在JavaScript中使用类型推断获取数组typeAry中元素的类型,通过实例演示hover时类型的显示,并关注于类型声明和数组元素类型的理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  type Ary = Array<{
    name: string;
    age: number;
  }>

如何取到数组中元素的类型, 我们可以用这种方式:

let item: Ary[number];

当我们hover在item上时,就会出现它的类型
在这里插入图片描述
可以看到我们取到我们想要的数组中的元素类型

<think>我们有一个需求:将对象数组中的所有属性值设置为 false。 注意:对象数组中的每个对象可能有多个属性,我们需要遍历数组中的每个对象,然后将每个对象的所有属性值都设置为 false。 步骤: 1. 遍历数组(可以使用map、forEach等)。 2. 对于数组中的每个对象,我们需要遍历这个对象的所有属性(键),然后将每个属性的值设置为false。 3. 由于对象可能包含多个属性,我们需要处理每个对象的每一个属性。 注意:我们并不改变对象的结构(即不改变属性名),只改变属性值。 实现方法: 方法1:使用map和Object.keys - 使用map遍历数组中的每个对象。 - 对于每个对象,使用Object.keys获取该对象的所有属性名,然后遍历这些属性名,将每个属性值设置为false。 - 注意:由于我们不想改变原数组(避免副作用),我们可以返回一个新对象,但原对象也会被改变(如果我们直接修改原对象)。根据需求,如果希望不改变原数组,则创建新对象;如果允许改变原数组,则可以直接修改。 方法2:使用forEach和for...in(注意for...in会遍历原型链上的属性,所以需要谨慎,通常配合hasOwnProperty使用)或者使用Object.keys。 根据需求,我们可能希望避免副作用,所以建议创建一个新的数组,新数组中的对象是修改后的新对象(深拷贝第一层,但属性值如果是引用类型,则还是共享的,但这里我们设置为false,所以没有问题)。 但是,题目要求是将对象数组的所有属性值设置为false,并没有要求不能改变原数组。所以我们可以根据实际情况选择。 然而,在TypeScript中,我们需要注意类型。原对象数组中的对象类型可能有多个属性,我们需要确保在设置false时,属性值原本的类型可能是多种(比如有的属性是boolean,有的可能是string、number等),但现在要求全部设置为boolean类型的false。 这可能会引起类型问题,因为原本的类型可能不是boolean。因此,我们需要考虑返回的对象的类型是否应该改变?如果原类型不允许属性为boolean,那么这样设置就会破坏类型约束。 所以,我们需要明确:这个操作会改变对象的类型结构,因此我们需要定义一个新的类型,或者使用一个更通用的类型(如将每个属性都改为boolean)?或者我们只处理那些原本就是boolean类型的属性? 但是,需求是“所有值”,所以不管原来是什么类型,现在都要变成false(即boolean类型)。因此,返回的对象的类型应该是一个新的类型,该类型的所有属性都是boolean类型。 然而,原对象数组的类型可能是已知的,比如: interface Student { id: number; name: string; passed: boolean; } 如果我们把所有的属性都设置为false,那么新的对象类型应该是: type AllFalse<T> = { [P in keyof T]: boolean }; 但是,我们并不需要这么复杂,因为需求是“设置为false”,所以我们可以直接生成一个新的对象,其属性名不变,但所有属性值都是false。 但是,如果我们在同一个数组中,每个对象的结构相同,那么我们可以使用一个映射类型来定义新对象的类型。 然而,在代码实现中,我们可能无法在运行时知道类型,所以我们需要在TypeScript中通过泛型或者重新声明类型来处理。 但是,用户的问题是如何实现,我们可以先不考虑复杂的类型转换,而是先实现功能,然后给出类型安全的实现。 有两种选择: 选择1:不改变原数组,返回一个新数组,新数组中的每个对象的所有属性值都是false。 选择2:直接修改原数组。 这里我们采用选择1,因为函数式编程更安全。 实现步骤: 1. 遍历数组,对每个对象进行转换。 2. 对于每个对象,创建一个新的空对象(或者复制原对象的属性结构,但值改为false)。 3. 遍历该对象的所有自身可枚举属性(使用Object.keys),将每个属性设置为false。 代码示例: let newArray = oldArray.map(obj => { let newObj = {}; // 创建一个新对象 Object.keys(obj).forEach(key => { newObj[key] = false; // 将每个属性设置为false }); return newObj; }); 但是,这样会丢失原对象的原型链,如果原对象有方法,那么也会被忽略(因为只处理可枚举的自有属性)。但是,题目是“对象数组”,通常我们处理的是数据对象,所以一般没有问题。 另外,如果对象有嵌套(即属性值也是对象),那么题目要求是“所有值”,包括嵌套对象的属性吗?从问题描述来看,是“对象数组的所有属性值”,所以应该只处理第一层属性。但是,问题并没有明确,我们按照字面意思,只处理第一层属性。 因此,我们只设置每个对象的第一层属性为false。 类型问题:由于我们将每个属性都设置为false,所以新对象的每个属性都是boolean类型(false)。所以,新数组的类型应该是Array<{ [key: string]: boolean }>,但这样会丢失具体的属性名。我们可以使用映射类型来转换原对象的类型。 在TypeScript中,如果我们希望保持属性名,但将每个属性都转换为boolean类型,我们可以这样定义: type MakeAllFalse<T> = { [P in keyof T]: boolean; }; 然后,我们可以这样写函数: function setAllPropertiesToFalse<T>(array: T[]): MakeAllFalse<T>[] { return array.map(obj => { const result: any = {}; // 使用any临时避免类型检查,后面我们会赋值 for (const key in obj) { if (obj.hasOwnProperty(key)) { result[key] = false; } } return result as MakeAllFalse<T>; }); } 但是,注意:如果对象上有从原型继承的属性,我们不应该处理(使用hasOwnProperty判断)。另外,如果对象有Symbol属性,Object.keys不会包括Symbol属性,所以如果需要处理Symbol属性,我们可以使用Object.getOwnPropertyNames和Object.getOwnPropertySymbols,但通常数据对象不会使用Symbol属性。 因此,我们使用Object.keys(它只包含可枚举的自有字符串属性)已经足够。 修改一下,使用Object.keys: function setAllPropertiesToFalse<T>(array: T[]): MakeAllFalse<T>[] { return array.map(obj => { const result: Partial<MakeAllFalse<T>> = {}; Object.keys(obj).forEach(key => { // 这里需要断言key是T的键 result[key as keyof T] = false; }); return result as MakeAllFalse<T>; }); } 但是,由于TypeScript的限制,我们可能无法完美地通过编译,因为编译器不知道key是T的键。但是,由于我们是从obj上获取的key,所以可以认为key是T的键。 另一种更简单的方式:使用类型断言,返回整个对象为MakeAllFalse<T>。 但是,如果原对象有可选属性,那么Object.keys可能不会包括那些值为undefined的可选属性(但可选属性如果不存在,则不会在obj上,所以不会被遍历到)。所以,我们只处理实际存在的属性。 因此,我们给出以下实现: function setAllPropertiesToFalse<T>(array: T[]): { [K in keyof T]: boolean }[] { return array.map(obj => { const newObj: { [K in keyof T]?: boolean } = {}; // 这里使用可选是因为我们可能不会设置所有属性(比如原型上的),但实际我们只设置自有属性 for (const key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = false; } } return newObj as { [K in keyof T]: boolean }; }); } 但是,上面的代码在设置属性时,如果原对象有索引签名,那么可以正常工作;如果没有,那么可能会遗漏一些属性(因为for..in循环会遍历继承的属性?但使用hasOwnProperty可以避免)。另外,我们使用类型断言强制将newObj转换成了要求返回的类型,但实际上我们确实设置了所有自有属性。 然而,上面的函数并不能保证返回的对象包含T的所有属性(比如,如果某个属性在T中定义,但在obj中不存在,那么就不会被设置)。所以,我们需要明确:我们只设置对象实际拥有的自有属性(可枚举的字符串属性)。如果原数组中每个对象的结构相同,那么这样处理是没问题的。 但是,如果数组中每个对象的结构不同(比如,有的对象有属性a,有的没有),那么返回的对象结构也不同。这可能是符合预期的。 因此,我们给出一个更简单的实现,并加上类型注释: function setAllPropertiesToFalse<T>(arr: T[]): Array<{ [K in keyof T]: boolean }> { return arr.map(obj => { const result: { [key: string]: boolean } = {}; Object.keys(obj).forEach(key => { result[key] = false; }); return result as { [K in keyof T]: boolean }; }); } 但是,这个类型断言可能不安全,因为T可能有非字符串类型的键(比如symbol或number),但Object.keys只返回字符串。所以,我们假设T的键都是字符串类型(通常情况)。 如果T的键包含symbol,那么我们需要单独处理symbol属性。我们可以使用Object.getOwnPropertySymbols来获取symbol属性。 但是,为了简单,我们假设对象只有字符串属性。 因此,最终实现如下(处理字符串和symbol属性): function setAllPropertiesToFalse<T>(arr: T[]): Array<{ [K in keyof T]: boolean }> { return arr.map(obj => { const result: Record<string | symbol, boolean> = {}; // 处理字符串属性 Object.keys(obj).forEach(key => { result[key] = false; }); // 处理symbol属性 Object.getOwnPropertySymbols(obj).forEach(sym => { result[sym] = false; }); return result as { [K in keyof T]: boolean }; }); } 但是,注意:我们使用Record<string | symbol, boolean>,然后强制转换为{ [K in keyof T]: boolean },这要求T的所有键都是string或symbol,否则会出错。 由于在TypeScript中,对象的键只能是string、number、symbol,而number会被转换为字符串。所以,我们也可以处理number键(通过Object.keys也会将数字键转换为字符串)。但是,Object.getOwnPropertyNames可以获取数字键(但也是字符串形式)。所以,我们使用Object.getOwnPropertyNames可以获取所有字符串和数字键(数字键会被转成字符串),而Object.getOwnPropertySymbols获取symbol键。 但是,我们也可以直接使用Reflect.ownKeys,它返回所有自有属性(包括字符串、symbol和不可枚举属性,但不可枚举属性我们是否要处理?题目要求所有值,所以应该包括不可枚举吗?通常,不可枚举属性不应该被处理,因为它们是内部属性。所以,我们只处理可枚举属性?题目没有明确,我们按照通常情况,只处理可枚举自有属性可能就足够了。 因此,我们使用Object.keys(可枚举自有字符串属性)和Object.getOwnPropertySymbols(自有symbol属性,包括可枚举和不可枚举?)——但Object.getOwnPropertySymbols返回所有自有symbol属性(不管是否可枚举)。所以,为了只处理可枚举属性,我们可以使用for...in循环(但for...in不包含symbol)和Object.keys也不包含symbol。所以,我们可能需要用Reflect.ownKeys然后过滤可枚举属性?这样复杂了。 考虑到需求是“对象数组的所有属性值”,通常我们只处理可枚举的自有属性(包括字符串和symbol)。所以,我们可以使用: const keys = [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj)]; 但是,这样会包括不可枚举的symbol属性,这可能是我们不希望的。 另一种方法是使用for...in循环(只包括可枚举的字符串属性)和Object.getOwnPropertySymbols(包括所有自有symbol属性,包括不可枚举)。这样不一致。 为了简单,我们只处理可枚举的自有属性(字符串和symbol),那么我们可以这样: function setAllPropertiesToFalse<T>(arr: T[]): Array<{ [K in keyof T]: boolean }> { return arr.map(obj => { const result: Record<PropertyKey, boolean> = {}; // 使用for...in循环遍历可枚举的字符串属性(包括继承的?但使用hasOwnProperty过滤) for (const key in obj) { if (obj.hasOwnProperty(key)) { result[key] = false; } } // 处理可枚举的symbol属性:但是如何获取可枚举的symbol属性?Object.getOwnPropertySymbols不区分可枚举性,我们可以通过propertyIsEnumerable来过滤。 const symbols = Object.getOwnPropertySymbols(obj); for (const sym of symbols) { if (obj.propertyIsEnumerable(sym)) { result[sym] = false; } } return result as { [K in keyof T]: boolean }; }); } 但是,这样代码比较长。在大多数情况下,数据对象不会使用symbol属性,所以我们可以忽略symbol属性,或者只处理字符串属性。 因此,我们根据实际需求来决定。如果用户的数据对象可能包含symbol属性,那么我们需要处理;否则,可以只处理字符串属性。 我们假设用户的数据对象都是普通的字符串键,所以我们可以简化处理。 最终,我们给出一个只处理字符串键(可枚举自有属性)的版本: function setAllPropertiesToFalse<T>(arr: T[]): Array<{ [K in keyof T]: boolean }> { return arr.map(obj => { const result: any = {}; // 使用any避免类型问题 Object.keys(obj).forEach(key => { result[key] = false; }); return result; }); } 但是,这个返回类型是Array<{ [K in keyof T]: boolean }>,而实际上我们返回的对象可能没有包含T中所有的键(比如,某个键在T中有,但在某个对象上不存在,那么就不会设置),所以这个类型断言不完全安全。但如果我们知道数组中的每个对象都有T的所有属性,那么就是安全的。 另一种方式:我们使用更宽松的返回类型,比如Record<string, boolean>[],但这样会丢失具体的属性名。 因此,我们可以这样定义: function setAllPropertiesToFalse<T>(arr: T[]): Array<Record<keyof T, boolean>> { // 这样要求返回的对象必须包含T的所有键,但实际我们可能没有设置,所以也不安全。 } 鉴于类型安全的问题,我们可以让调用者知道,这个函数返回的对象只包含实际在对象上存在的属性(这些属性被设置为false),并且类型都转为boolean。 我们可以使用: function setAllPropertiesToFalse<T>(arr: T[]): Array<{ [K in keyof T]?: boolean }> { // 使用可选,因为可能有些属性不存在 } 或者,我们可以使用Partial: function setAllPropertiesToFalse<T>(arr: T[]): Array<Partial<{ [K in keyof T]: boolean }>> { // 这样表示返回的对象可能只包含部分属性 } 但是,这样返回的类型中,每个属性都是可选的,可能也不是很理想。 根据实际需求,我们可能不需要这么严格的类型。因此,我们可以这样: function setAllPropertiesToFalse<T extends object>(arr: T[]): Array<Record<string, boolean>> { return arr.map(obj => { const result: Record<string, boolean> = {}; Object.keys(obj).forEach(key => { result[key] = false; }); return result; }); } 这样,返回类型是Record<string, boolean>[],表示每个对象都是一个字符串键到boolean的映射。这样,调用者可以使用任意字符串索引,但会丢失具体的属性名信息。 如果调用者需要具体的属性名信息,那么可能需要使用其他方式(比如传入一个属性名列表),或者由调用者自己处理类型转换。 考虑到简单性,我们可以这样实现,并告知调用者返回的对象类型是Record<string, boolean>。 但是,用户的问题是在TypeScript中,所以我们希望尽可能提供类型安全。 另一种折中方案:使用泛型,但返回一个Partial<T>但将所有属性值改为boolean?这样也不对,因为Partial<T>保留原类型。 我们可以这样定义类型: type Falseify<T> = { [K in keyof T]?: boolean; } 然后返回Falseify<T>[]。 但是,这样我们只是将原类型T的每个属性都变成可选的boolean类型。这样,返回的对象可以包含T的任意属性(但都是boolean),而且可能不包含所有属性(因为可选)。 所以,函数可以这样: function setAllPropertiesToFalse<T>(arr: T[]): Falseify<T>[] { return arr.map(obj => { const result: Falseify<T> = {}; for (const key in obj) { if (obj.hasOwnProperty(key)) { // 这里需要断言,因为K in keyof T,但key可能是T的键 result[key as keyof T] = false; } } return result; }); } 这样,返回的类型是Falseify<T>[],其中Falseify<T>将T的所有属性都变为可选的boolean类型。这样,调用者可以使用返回的对象,但需要知道某些属性可能不存在(因为原对象上可能没有这个属性)。 但是,如果原对象上存在某个属性,那么返回的对象上就会有这个属性(且为false);如果原对象上没有,那么返回的对象上就没有。所以,使用可选是合理的。 因此,我们采用这个方案。 代码实现: type Falseify<T> = { [K in keyof T]?: boolean; }; function setAllPropertiesToFalse<T>(arr: T[]): Falseify<T>[] { return arr.map(obj => { const result: Falseify<T> = {}; for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { result[key as keyof T] = false; } } return result; }); } 注意:我们使用Object.prototype.hasOwnProperty.call来避免obj可能覆盖了hasOwnProperty方法。 另外,我们使用for...in循环,它会遍历可枚举的自有属性和继承属性,但我们用hasOwnProperty过滤了,所以只包括自有属性。 测试: const arr = [ { a: 1, b: true }, { c: 'hello', d: 2 } ]; const result = setAllPropertiesToFalse(arr); // result[0] 应该是 { a: false, b: false } // result[1] 应该是 { c: false, d: false } 但是,arr中两个对象的结构不同,所以返回的Falseify<T>中,T会被推断为联合类型{ a: number, b: boolean } | { c: string, d: number },那么Falseify<T>就会是Falseify<{ a: number, b: boolean }> | Falseify<{ c: string, d: number }>,即: { a?: boolean, b?: boolean } | { c?: boolean, d?: boolean } 这样,当我们访问result[0].a时,类型为boolean | undefined,因为可能是另一个对象类型。 所以,这个函数在数组元素类型不一致时,返回的数组元素类型是联合类型,使用起来可能不太方便。 如果数组元素类型一致,那么T就是该类型,Falseify<T>就是确定的。 因此,我们提供了这个类型安全的版本。 最后,如果用户希望修改原数组而不是返回新数组,我们可以提供另一个函数,但这里我们按照需求返回新数组。 因此,最终答案: 我们可以定义一个Falseify类型,然后实现setAllPropertiesToFalse函数。 但是,如果用户不需要类型这么严格,也可以使用更简单的版本,比如返回Record<string, boolean>[]。 我们根据用户需求选择,这里我们给出一个类型相对安全的版本。 代码: type Falseify<T> = { [K in keyof T]?: boolean; }; function setAllPropertiesToFalse<T>(arr: T[]): Falseify<T>[] { return arr.map(obj => { const result: Falseify<T> = {}; for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { // 将属性设置为false result[key as keyof T] = false; } } return result; }); } 注意:这个函数不会处理symbol属性,如果需要,可以添加symbol属性的处理(如前面所述)。 另外,如果对象上有不可枚举属性,它们不会被处理。 因此,我们完成了这个需求。 如果用户希望处理symbol属性,我们可以修改为: function setAllPropertiesToFalse<T>(arr: T[]): Falseify<T>[] { return arr.map(obj => { const result: Falseify<T> = {}; // 处理字符串和symbol属性(可枚举的自有属性) // 使用Reflect.ownKeys可以获取所有自有属性(包括不可枚举和symbol),但我们需要过滤可枚举的 // 但是,没有直接的方法获取所有可枚举的自有属性(包括symbol),所以我们可以分别处理: // 字符串属性(包括数字)使用for...in(配合hasOwnProperty)和symbol属性使用Object.getOwnPropertySymbols配合propertyIsEnumerable // 处理字符串属性(for...in循环) for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { result[key as keyof T] = false; } } // 处理symbol属性 const symbols = Object.getOwnPropertySymbols(obj); for (const sym of symbols) { if (obj.propertyIsEnumerable(sym)) { // 将symbol作为key,但Falseify<T>中可能不包含symbol(因为keyof T默认不包含symbol),所以我们需要特殊处理。 // 但是,Falseify<T>中,K in keyof T,而keyof T可能包含symbol类型(如果T有symbol键的话)。 // 所以,我们可以将sym作为keyof T,但需要确保T有symbol键。如果T没有,那么我们就不能设置,或者设置到result上,但类型会出错。 // 因此,我们这里使用类型断言:sym as unknown as keyof T result[sym as unknown as keyof T] = false; } } return result; }); } 但是,这样可能会引入类型不安全,因为如果T没有这个symbol键,那么我们设置了一个不存在的属性。所以,只有当T的键包含symbol时,我们才能设置。否则,这个属性在返回的对象中,但Falseify<T>中不包含,所以类型上是不允许的。 因此,我们建议,如果用户的数据对象可能包含symbol键,那么应该提前定义在T中,否则我们不应该设置。 所以,在一般情况下,我们建议只处理字符串属性,因为symbol属性很少在数据对象中使用。 因此,我们提供只处理字符串属性的版本。 最终代码(只处理字符串属性):</think>在 TypeScript 中,将对象数组的所有属性值设置为 `false` 可以通过以下步骤实现: ### 解决方案 ```typescript // 定义泛型函数 function setAllPropertiesToFalse<T>(arr: T[]): Array<Record<keyof T, boolean>> { return arr.map(obj => { const newObj: Record<string, boolean> = {}; // 遍历对象的所有自有属性 Object.keys(obj).forEach(key => { newObj[key] = false; // 设置属性值为 false }); return newObj as Record<keyof T, boolean>; }); } // 使用示例 interface User { id: number; name: string; isActive: boolean; } const users: User[] = [ { id: 1, name: "Alice", isActive: true }, { id: 2, name: "Bob", isActive: true } ]; const updatedUsers = setAllPropertiesToFalse(users); console.log(updatedUsers); /* 输出: [ { id: false, name: false, isActive: false }, { id: false, name: false, isActive: false } ] */ ``` ### 关键说明 1. **泛型函数**:`<T>` 保留输入数组的原始对象类型。 2. **类型转换**: - 使用 `Record<keyof T, boolean>` 将输出类型定义为所有属性均为 `boolean` 的新对象。 - 通过 `as Record<keyof T, boolean>` 确保类型安全[^1]。 3. **属性遍历**: - `Object.keys(obj)` 获取对象所有自有属性的键。 - `forEach` 遍历每个键并将其值设为 `false`。 4. **不修改原数组**:通过 `map()` 创建新数组,避免副作用[^2]。 ### 处理嵌套对象 如果需要递归处理嵌套对象(将深层属性也设为 `false`): ```typescript function deepSetFalse(obj: any): any { if (typeof obj === "object" && obj !== null) { Object.keys(obj).forEach(key => { obj[key] = deepSetFalse(obj[key]); // 递归处理 }); return obj; } return false; // 非对象值直接返回 false } const nestedUsers = deepSetFalse(users); ``` ### 注意事项 - **符号属性**:若对象包含 `Symbol` 类型属性,需额外使用 `Object.getOwnPropertySymbols()` 处理。 - **继承属性**:`Object.keys()` 仅处理自有属性,原型链上的属性不会被修改[^3]。 - **严格类型**:实际业务中建议定义更精确的接口替代 `any` 类型。 --- ### 相关问题 1. **如何保留其他属性类型?** 若需仅将特定属性(如布尔类型)设为 `false`,可使用条件类型筛选: ```typescript type BooleanKeys<T> = { [K in keyof T]: T[K] extends boolean ? K : never }[keyof T]; ``` [^4] 2. **性能优化方案?** - 对大数组使用 `for` 循环替代 `map` + `forEach` - 避免嵌套对象递归以降低时间复杂度 - 使用 Web Worker 分流计算密集型任务 3. **如何确保不可变数据?** 结合 `Object.freeze()` 或库(如 Immer)实现不可变更新: ```typescript import produce from "immer"; const immutableUpdate = produce(users, draft => { draft.forEach(obj => Object.keys(obj).forEach(k => { obj[k] = false; })); }); ``` 4. **如何处理可选属性?** 使用 `Partial<Record<keyof T, boolean>>` 类型,允许部分属性缺失: ```typescript function setPartialFalse<T>(arr: T[]): Partial<Record<keyof T, boolean>>[] { // 实现逻辑 } ``` [^1]: TypeScript 泛型和类型映射可创建灵活的类型转换。 [^2]: 函数式编程原则建议避免直接修改输入数据。 [^3]: `Object.keys()` 仅返回对象自有可枚举属性键。 [^4]: 条件类型可基于属性值类型进行动态类型筛选。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值