《TyptScript语言手册》第1章-介绍

1.介绍
      如Web邮件、地图、文档编辑和协作工具等JavaScript应用程序正在成为日常编程工作中越来越重要的组成部分。我们设计TypeScript以满足需要使用JavaScript编程的团队建立和维护大型JavaScript程序。TypeScript可以帮助编程团队定义软件组件之间的接口,并深入了解现有的JavaScript库。通过将代码组织到动态可加载模块,也使团队减少命名冲突。TypeScript的可选的类型系统使JavaScript程序员使用高效开发工具并实践:静态类型检查、平衡导航、代码自动完成、代码重构。

    TypeScript是JavaScript的一种语法糖。TypeScript是ECMAScript的5(ES5)语法的超集。每一个JavaScript程序也是一个TypeScript程序。TypeScript编译器只在本地执行转换,并且不会重新定义TypeScript声明的变量。这导致输出的JavaScript代码非常接近TypeScript代码。TypeScript不改变变量名,这使得JavaScript更易于调试。TypeScript提供可选代码地图,可以进行源代码级调试。TypeScript通常根据保存的文件生成JavaScript代码,在JavaScript开发中常常保存测试、编辑和刷新周期。

   TypeScript语法包括几个Ecmascript 6(ES6)的特点,包括类和模块。类使程序员能够用一种标准的方式表达常见的面向对象模式,使得像继承这种特性更具可读性和可操作性。模块使程序员将他们的代码转换为元件,同时避免命名冲突。TypeScript编译器提供了模块编译选项,支持静态和动态加载模块。

    TypeScript系统还为JavaScript提供了可选类型注释。这些类型的注释是像在封闭的系统中发现的JSDoc注释,但在TypeScript中它们被直接集成到语法中。这种整合使得代码更易读,并减少同步类型注释与它们对应变量的维护成本。

   TypeScript类型系统使程序员能够使用JavaScript限制的功能,以及使用增强改能力的工具。为了最大限度地减少注释数量,使得工具更有用,TypeScript类型系统大量使用类型推断。例如,从下面的语句,TypeScript会推断出变量的“i”的类型。
[JavaScript]  纯文本查看  复制代码
?
var i = 0;

     TypeScript将从以下函数定义推断出函数返回类型字符串。
[JavaScript]  纯文本查看  复制代码
?
function f() {
return "hello" ;
}

     受益于这一能力,程序员可以使用TypeScript的语言服务。例如,一个代码编辑器可以包含TypeScript的语言服务,并且用服务找到一个字符串对象的成员如下面屏幕截图。

file:///C:/Users/ZHAOXI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg
     在这个例子中,程序员在代码没有提供类型注释的时候可以受益。一些有益的工具,然而,需要程序员提供类型注释。在TypeScript,我们可以表达一个参数需要如下面的代码片段。

[JavaScript]  纯文本查看  复制代码
?
function f(s: string){
return s;
}
f({});       //Error
f( "hello" );  // Ok

     这个可选类型注释参数s让TypeScript类型检查器知道程序员期望参数“s”类型为“string”。在函数f内部,工具可以假设“s”的类型是字符串,并提供操作类型检查和成员完成与假设是一致的。第一次调用函数“f”的时候会发出错误,,因为“f”期望参数是一个字符串,而不是一个对象。对于函数f,TypeScript编译器将生成以下JavaScript代码:
[JavaScript]  纯文本查看  复制代码
?
function f(s) {
     return  s;
}

在JavaScript输出中,所有的类型注解都被删除掉。一般来说,TypeScript在生成JavaScript之前会删除掉所有类型信息。

1.1 环境声明
环境声明向TypeScript中引入了一个变量,但对于生成的JavaScript程序没有任何影响。程序员可以使用环境声明告诉TypeScript编译器,其他一些组件将提供一个变量。例如,默认情况下在使用未定义的变量时,TypeScript编译器将生成一个错误。要添加一些由浏览器定义的公共变量,TypeScript程序员可以使用这些环境声明。下面的示例声明通过浏览器提供的“document”对象。由于声明没有指定类型,该类型'any'可以进行推断。类型“any”是指一种工具可以假设没有任何关于文档对象的形状或行为。以下一些例子将说明程序员如何可以使用类型来进一步描述对象的预期行为。
[JavaScript]  纯文本查看  复制代码
?
declarevar document;
document.title = "Hello" // 可以这样使用,因为document已经声明过了

在“document”的这种情况下,编译器会自动提供一个声明,因为TypeScript文件包含一个默认的lib.d.ts,它提供的接口声明了内置的JavaScript库以及文档对象模型。
文稿编译器默认不包括jQuery的接口,所以使用jQuery时,程序员可以提供一个声明,如:
[JavaScript]  纯文本查看  复制代码
?
declarevar $;

第1.3节提供了一个更广泛的例子,一个程序员可以为jQuery和其他库添加类型信息。

1.2函数类型
函数表达式是JavaScript的一个强大的功能。它们使函数可以创建闭包:可以从函数定义中捕捉周围的的词法信息。闭包是JavaScript目前唯一的执行数据封装的方式。通过捕获和使用环境变量,闭包可以保留无法从封闭外访问的信息。 JavaScript程序员经常使用闭包来表达事件处理和其他异步回调,在其他软件组件中,如DOM,会回调到JavaScript的一个处理函数。

TypeScript的函数类型,使程序员表达函数的预期的签名成为可能。函数签名是一个序列参数类型以及返回类型。下面的示例使用函数类型来表示的异步投票机制的回调签名要求。

[JavaScript]  纯文本查看  复制代码
?
function vote(candidate: string,callback: (result: string ) =>any) {
// ...
}
vote( "BigPig" ,
function (result: string ) {
if (result === "BigPig" ) {
// ...
          }
      }
);


在这个例子中,第二个参数“vote”拥有函数类型
[JavaScript]  纯文本查看  复制代码
?
(result: string )=>any

这意味着第二个参数是一个函数,返回类型为'any',含有一个参数'string',名为'result'。

第3.7.2节提供了有关函数类型的附加信息。

1.3对象类型
TypeScript程序员使用对象类型来声明对象行为的期望。下面的代码使用的对象类型来指定“MakePoint”函数的返回类型。
[JavaScript]  纯文本查看  复制代码
?
var MakePoint: () => {
     x:  number; y: number ;
};

程序员可以给名字对象类型;我们调用一个名为对象类型的接口。例如,下面的代码,接口声明了一个必填字段(名称)和一个可选字段

[JavaScript]  纯文本查看  复制代码
?
(favoriteColor)。
interface Friend {
     name: string ;
     favoriteColor?: string ;
}
function add(friend:Friend) {
var name = friend.name;
}
add({name: "Fred" });  // Ok
add({ favoriteColor: "blue" });  // 错误, 必须传入name参数
add({ name: "Jill" ,favoriteColor: "green" });  // Ok


TypeScript对象类型模型的行为,一个JavaScript对象可以表现出的多样性。举例来说,jQuery库定义了一个对象,'$',有方法,如'get'(它发送一个Ajax消息),和字段,如“browser”(它提供了浏览器的信息)。然而,jQuery的用户还可以调用'$'作为一个函数。这个函数的行为取决于传递给函数的参数类型。

下面的代码片段捕捉到jQuery行为的一小部分,就足够使用jQuery的一个简单方法。

[JavaScript]  纯文本查看  复制代码
?
interfaceJQuery {
     text(content: string );
}
 
interface JQueryStatic {
     get(url: string , callback: (data: string)=> any); 
     (query: string ): JQuery;
}
declarevar $: JQueryStatic;
function (data: string ) {
          $( "div" ).text(data);
       }
);


“JQueryStatic”接口引用了另一个接口:“JQuery”。此接口表示的一个或多个DOM元素的集合。 jQuery库可以在这样一个集合中执行许多操作,但在这个例子中,jQuery的客户端只需要知道它可以通过传递一个字符串“text”的方法设置每个jQuery的元素在集合中的文本内容。在“JQueryStatic”接口中还包含了一个方法,“get”,执行一个Ajax获得所提供的网址操作,并在接收到响应时调用回调。
最后,“JQueryStatic”接口中包含了裸露的函数签名。
[JavaScript]  纯文本查看  复制代码
?
(query: string ):JQuery;

裸露的签名表示该接口的实例可以被调用。这个例子说明,TypeScript函数类型是TypeScript对象类型的一种特殊情况。具体地,函数类型是含有一个或多个调用特征的对象类型。出于这个原因,我们可以写任何函数类型为对象类型。下面的示例使用这两种形式来描述的相同类型。
[JavaScript]  纯文本查看  复制代码
?
var f: { ():  string; };
var sameType: () => string = f; // Ok
var nope: () => number  = sameType;  // 错误: 类型不匹配

上面我们提到,'$'功能表现的不同取决于它的参数的类型。到目前为止,我们的jQuery输入只能捕获这些行为之一:当传递一个字符串返回类型的对象“JQuery”。要指定多个行为,typescript支持函数签名的对象类型重载。例如,我们可以添加一个额外的调用签名的“JQueryStatic”接口
[JavaScript]  纯文本查看  复制代码
?
(ready: () => any): any;

此签名表示该函数可作为'$'函数的参数进行传递。当一个函数传递给'$',当DOM文档准备就绪时,jQuery库将调用该函数。由于typescript支持重载,工具可以用typescript来显示所有可用的函数签名与他们的文档的提示,并给予当一个函数被调用特定的签名正确的文档。

典型客户端将不需要添加任何额外的输入,但可以只使用一个社区提供的输入发现(通过语句完成了文件的提示),并验证(通过静态检查)正确使用类库,如下面的屏幕截图。

file:///C:/Users/ZHAOXI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image004.jpg
第3.3节提供了有关对象类型的附加信息。

1.4结构子类型化
对象类型是比较结构。例如,下面的代码片段,类'CPoint'匹配接口'Point',因为'CPoint'有所有“Point”所要求的成员。一个类可以选择性地声明它实现一个接口,这样编译器会检查声明中结构的兼容性。这个例子也说明了,一个对象类型可以从一个对象字面的意思推断出类型。只要这个对象支持所有必要的成员。

[JavaScript]  纯文本查看  复制代码
?
interface Point  {
     x: number;
     y: number;
}
function getX (p : Point )  {
     return  p.x;
}
class CPoint  {
     x: number ;
     y: number ;
     constructor(x:  number , y: number )  {
this .x = x;
this .y = y;
}
}
getX( new CPoint (0,  0 ));   //可以, 字段匹配
getX({ x:  0 , y: 0 , color: "red"  });   // 可以多出额外的字段
getX({ x:  0  });   // 错误: 参数不匹配


请参见3.8节有关类型比较的详细信息。

1.5上下文类型
通常情况下,TypeScript类型推断所得“自下而上”:从表达式树的叶子找到它的根。在下面的例子中,TypeScript所推导'number'作为函数'mul'的返回类型,通过返回表达式的返回类型。

[JavaScript]  纯文本查看  复制代码
?
function mul(a: number,b: number ) {
return a * b;
}

对于变量和参数没有类型注释或默认值,TypeScript推断输入“any”,确保编译器并不需要对函数的调用点的非本地信息来推断函数的返回类型。一般情况下,这种自下而上的方式为程序员提供了有关类型的信息一个明确的直觉。

然而,在一些有限的环境中,推理前进从表达式的上下文中的“自上而下”。在这情况下,它被称为上下文类型。上下文帮助输入工具提供当程序员使用的是类型,但可能不知道所有类型的详细信息,优良信息。例如,在jQuery的示例中,如上所述,程序员提供一个函数表达式作为第二个参数为“get”的方法。在输入的表达方式,工具可以假设函数表达式的类型是给出的'get'的签名,可以提供包括参数名称和类型的模板。

[JavaScript]  纯文本查看  复制代码
?
function (data) {
          $( "div" ).text(data);  // TypeScript 推断出 data 是一个字符串类型
       }
);

上下文类型对于写出对象常量也是有益的。作为编程类型的对象字面,上下文类型的提供,使工具提供了完成对象成员名称的信息。

第4.19节提供了有关情境类型的表达式的更多信息。

1.6 类
JavaScript实践至少有两个常见的设计模式:模块模式和类模式。粗略地讲,模块模式使用闭包隐藏名称和封装的私人数据,而类模式在面向对象的继承机制的基础上使用原型链来实现许多变体。类库如“prototype.js”是典型的这种做法。

这一节和下面的模块部分将展示TypeScript生成一致的、惯用的JavaScript代码来实现类和模块,与当前ES6的建议一致。TypeScript的翻译的目的是发出什么程序员会在实现一个类或模块独立的工具。这一部分还将讨论TypeScript如何推断为每个类声明一个类型。我们将从一个简单的BankAccount类开始。

[JavaScript]  纯文本查看  复制代码
?
class BankAccount {
balance = 0;
     deposit(credit: number ) {
this .balance += credit;
returnthis.balance;
     }
}
这个类生成以下JavaScript代码。
[JavaScript]  纯文本查看  复制代码
?
var BankAccount =( function () {
function BankAccount() {
this .balance = 0 ;
     }
     BankAccount.prototype.deposit = function (credit){
this .balance += credit;
returnthis.balance;
     };
return BankAccount;
})();

这typescript类声明创建了一个名为“BankAccount”的变量,它的值是构造函数“BankAccount”的实例变量。该声明还创建了同名的实例类型。如果我们写这类型的接口,它看起来像下面这样。

[JavaScript]  纯文本查看  复制代码
?
interface BankAccount{
     balance: number ;
     deposit(credit: number ): number;
}

如果我们要写出“BankAccount”构造函数的函数类型声明变量,它将有以下形式。

[JavaScript]  纯文本查看  复制代码
?
var BankAccount: new ()=> BankAccount;

函数签名的前缀是关键字'new'表明'BankAccount“函数必须调用一个构造函数。这是可能的函数的类型来有两个呼叫和构造特征。例如,内置的JavaScript日期对象的类型包括各种签名。

如果我们要开始我们的银行账户的初始余额,我们可以添加到“BankAccount”类的构造函数声明。

[JavaScript]  纯文本查看  复制代码
?
class BankAccount {
     balance:number ;
constructor(initially: number ) {
this .balance = initially;
     }
     deposit(credit: number ) {
this .balance += credit;
returnthis.balance;
     }
}

这个版本的“BankAccount”类的要求我们引入一个构造函数的参数,然后将其分配给'balance'字段。为了简化这种常见的情况,typescript接受以下简写语法。

[JavaScript]  纯文本查看  复制代码
?
class BankAccount {
constructor(public balance: number) {
     }
     deposit(credit: number ) {
this .balance += credit;
returnthis.balance;
     }
}

在“public”关键字表示该构造函数的参数是要被保留的字段。Public是默认可见性的,但程序员也可以指定私有成员。私人能见度设计时结构;它是在静态类型检查执行但并不意味着任何运行时执行。
Typescript类也支持继承,如下面的例子。

[JavaScript]  纯文本查看  复制代码
?
class CheckingAccount  extends BankAccount {
     constructor( balance:  number)  {
          super (balance );
     }
     writeCheck (debit:  number)  {
          this .balance  -= debit;
     }
}

在此示例中,类“CheckingAccount'源于类'BankAccount'。该构造函数“CheckingAccount'调用构造函数的类”BankAccount'使用'super'关键字。在生成的JavaScript代码,“CheckingAccount“的原型是”BankingAccount“。

Typescript类也可以指定静态成员。静态类成员成为类的构造函数的一个属性。

第8部分提供了额外的信息类。

1.7枚举类型
Typescript使程序员可以总结出一套数字常量作为一个枚举类型。下面的示例创建一个枚举类型来表示一个计算器计算。

[JavaScript]  纯文本查看  复制代码
?
enum Operator {
     ADD,
     DIV,
     MUL,
     SUB
}
function compute(op:Operator, a: number, b: number ) {
     console.log( "the operator is" +Operator[op]);
// ...
}


在此示例中,计算函数记录用枚举类型的特征的运算符'op':从枚举值('op')到对应于该值的字符串反向映射。例如,“Operator”的声明将自动分配的整数,从零开始,所列出的枚举成员。第9章介绍了如何编程人员也明确分配整数枚举成员,并且可以使用任何字符串来命名枚举成员。

如果所有的枚举成员都明确分配整数,或者枚举了所有成员自动分配,TypeScript编译器将生成一个枚举成员对应于该成员的分配值(标注有注释)一个JavaScript不变。这提高了很多JavaScript引擎的性能。

例如,“compute”功能可能包含类似下面的switch语句。

[JavaScript]  纯文本查看  复制代码
?
switch (op) {
case Operator.ADD:
// execute add
break ;
case Operator.DIV:
// execute div
break ;
// ...
     }
对于这个switch语句,编译器将生成以下代码。
[JavaScript]  纯文本查看  复制代码
?
switch (op) {
case 0 /* Operator.ADD */ :
// execute add
break ;
case 1 /* Operator.DIV */ :
// execute div
break ;
// ...
     }

JavaScript函数实现可以使用这些明确的常量,为这个switch语句生成高效的代码。例如,通过建立值索引的跳转表。

1.8重载字符串参数
TypeScript的一个重要目标是为现有的JavaScript编程模式提供准确而直接的类型。为此,TypeScript包括泛型类型,在下一节讨论,并就重载字符串参数,本节的主题。

JavaScript编程接口通常包括函数,其行为被传递给函数的字符串常量识别。文档对象模型大量使用这种模式。例如,下面的屏幕截图显示了“document”对象的“createElement”的方法有多个签名,其中一些确定类型时,特定的字符串被传递到方法中返回。

file:///C:/Users/ZHAOXI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image006.jpg
下面的代码片段使用此功能。因为'span'变量推断有类型“HTMLSpanElement”,代码可以参考没有静态错误的“span”属性“isMultiline“。

[JavaScript]  纯文本查看  复制代码
?
var span =document.createElement( "span" );
span.isMultiLine= false // OK: HTMLSpanElement has an 'isMultiline' property


在下面的屏幕截图,编程工具结合信息从重载字符串参数与上下文类型推断的类型变量“e”是“MouseEvent”,因此“e”有“clientX”属性。
file:///C:/Users/ZHAOXI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image008.jpg
3.7.2.4节详细介绍了如何使用字符串函数签名。

1.9 泛型类型和功能
如同重载的字符串参数,泛型类型更容易为TypeScript准确地捕捉JavaScript库的行为。因为它们使类型信息,从客户端的代码流,通过库中的代码,并返回到客户端的代码,泛型类型可以做的比其他任何TypeScript功能,支持详细的API描述。
为了说明这一点,让我们来看看TypeScript接口,内置的JavaScript数组类型的一部分​​。您可以在伴随着TypeScript分布“lib.d.ts”文件此接口。

[JavaScript]  纯文本查看  复制代码
?
interface Array<T>{
     reverse(): T[];
     sort(compareFn?: (a: T, b: T) =>number ): T[];
// ...
}

接口定义,像上面的,可以有一个或多个类型参数。在这种情况下,“Array”接口具有一个参数,’T‘,它定义为数组元素类型。在“reverse”方法返回相同的元素类型的数组。排序方法接受一个可选的参数,“compareFn”,其类型是一个函数,它接受输入'T'的两个参数并返回一个数字。最后,返回排序与元素类型“T”的数组。

功能也可以有通用的参数。例如,阵列接口包含一个“map”的方法,定义如下:

[JavaScript]  纯文本查看  复制代码
?
map<U>(func: (value:T, index: number , array: T[]) => U, thisArg?: any): U[];

在地图的方法,数组'A'与元素类型’T‘,将适用于功能“FUNC”以'A'的每个元素上调用,返回类型为“U”的值
TypeScript编译器通常可以推断出泛型方法的参数,使其不需要程序员显式地为他们提供。在下面的例子中,编译器推断地图方法的参数是“U”类型已经“string”,因为通过映射函数返回一个字符串。

[JavaScript]  纯文本查看  复制代码
?
function numberToString(a: number[]){
var stringArray = a.map(v => v.toString());
return stringArray;
}

编译器推断在这个例子中,该“numberToString'函数返回字符串的数组。

在TypeScript,类也可以有类型参数。下面的代码声明一个实现类型’T‘的项目链表一类。这段代码说明程序员如何约束类型参数来扩展一个特定的类型。在这种情况下,列表上的项目必须扩展类型'NamedItem'。这使得程序员可以实施“log”功能,它记录了项目的名称。

[JavaScript]  纯文本查看  复制代码
?
interface NamedItem {
     name: string ;
}
class List<T extends NamedItem>{
     next: List<T> = null ;
constructor( public item:T) {
     }
    insertAfter(item: T) {
var temp = this .next;
this .next = new List(item);
this .next.next = temp;
     }
     log() {
         console.log( this .item.name);
     }
// ...
}


第3.5节提供了进一步的泛型类型信息。

1.10 模块
类和接口提供了一种机制,用于描述如何使用,可以从该组件的实现中分离出来的软件组件支持大规模JavaScript开发。TypeScript强制在设计时类(通过限制使用私有成员)实施的封装,但不能在运行时执行封装,因为所有对象的属性在运行时访问。 JavaScript的未来版本可能会提供专用的名称,这将使运行时执行私有成员。
在当前版本的JavaScript,在运行时执行封装的唯一方法是使用模块模式:封装私有字段和方法使用闭包变量。模块模式是一种自然的方式来提供组织结构和动态加载选项通过边界软件组件。一个模块还可以提供介绍名称空间的能力,避免使用全局名称空间的大多数软件组件。
下面的例子展示了JavaScript模块模式。

[JavaScript]  纯文本查看  复制代码
?
( function (exports)  {
     var key  = generateSecretKey();
     function sendMessage (message)  {
         sendSecureMessage (message,  key);
     }
exports.sendMessage = sendMessage;
})(MessageModule);

这个例子说明了模块模式的两个基本要素:一个模块闭包和模块对象。模块闭包是一个函数,用于封装模块的实现,在这种情况下,变量“key”和函数“sendMessage”。该模块对象包含模块的输出变量和函数。简单的模块可以创建并返回的模块对象。上述模块将模块对象作为参数,'exports',并增加了“sendMessage'属性的模块对象。这种方法简化了模块的动态加载,同时还支持分离模块代码分割成多个文件。

该示例假定外词法范围定义函数的generateSecretKey“和”sendSecureMessage';它还假定该外范围已分配模块对象的变量'MessageModule'。
TypeScript模块提供了一种机制,简明扼要地表达模块的模式。在TypeScript,程序员可以由外部模块内嵌套模块和类结合的模式类模块模式。

下面的示例显示了定义和使用一个简单的模块。

[JavaScript]  纯文本查看  复制代码
?
module M {
     var s = "hello" ;
     export function f() {
         return s;
     }
}
M.f();
M.s;  // Error, s is not exported


在这个例子中,变量's'的是模块的一个私人特征,但函数“f”被从模块导出和访问代码模块的外部。如果我们要描述模块的“M”的接口和变量方面的影响,我们会写

[JavaScript]  纯文本查看  复制代码
?
interface M  {
     f(): string;
}
var M: M;


该界面的“M”总结模块的外部可见的行为,“M”。在这个例子中,我们可以使用相同的名称,因为TypeScript类型名和变量名的接口作为初始化的变量不冲突:每个词法范围包含变量声明空间和类型的声明空间(详情参见2.3节) 。

模块“M”是一个内部模块的一个例子,因为它嵌套在全局模块(详情参见第10节)中。TypeScript编译器发出此模块下面的JavaScript代码。
[JavaScript]  纯文本查看  复制代码
?
var M;
( function (M) {
     var  s = "hello" ;
     function  f() {
         return  s;
     }
     M.f = f;
})(M||(M={}));

在这种情况下,编译器假定模块对象驻留在全局变量“M”,这可能会或可能不会被初始化为所需的模块对象。

TypeScript也支持外接模块,这是包含顶级出口和进口的指令文件。对于这种类型的模块的TypeScript编译器将发出代码的模块,关闭和模块对象实现按指定的动态加载系统各不相同,例如,异步模块定义系统。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值