当前位置: 首页 > Web前端 > HTML5

TypeScript的Class(下)

时间:2023-04-05 17:23:29 HTML5

TypeScript的官方文档已经更新,但我能找到的中文文档还是旧版本。因此,对一些新增和修改的章节进行了翻译和整理。本文翻译自《TypeScriptHandbook》中的“类”一章。本文并没有严格按照原文翻译,部分内容也做了说明和补充。静态成员(StaticMembers)类可以有静态成员,静态成员与类实例无关,可以通过类本身访问:classMyClass{staticx=0;静态printX(){console.log(MyClass.x);}}console.log(MyClass.x);MyClass.printX();静态成员也可以使用publicprotected和private可见性修饰符:classMyClass{privatestaticx=0;}console.log(MyClass.x);//属性'x'是私有的,只能在类'MyClass'中访问。静态成员也可以被继承:classBase{staticgetGreeting(){return"Helloworld";}}classDerivedextendsBase{myGreeting=Derived.getGreeting();}特殊静态名称(SpecialStaticNames)类本身是一个函数,一般认为覆盖Function原型上的属性是不安全的,所以一些fixedstatic不能使用名称。name、length、call等函数属性不能用于定义静态成员:classS{staticname="S!";//静态属性'name'与构造函数'S'的内置属性'Function.name'冲突。}为什么没有静态类?(为什么没有静态类?)TypeScript(和JavaScript)没有称为静态类的结构,但像C#和Java一样。所谓静态类是指作为类的静态成员存在于类内部的类。例如://javapublicclassOuterClass{privatestaticStringa="1";静态类InnerClass{privateintb=2;}}之所以存在静态类,是因为这些语言强制所有的数据和函数都在一个Class中,但是TypeScript中不存在这种限制,所以不需要静态类。只有一个实例的类可以用JavaScript/TypeScript中的普通对象代替。例如,我们不需要静态类语法,因为TypeScript中的常规对象(或顶级函数)可以做同样的事情://Unnecessary"static"classclassMyStaticClass{staticdoSomething(){}}//首选(备选方案1)functiondoSomething(){}//首选(备选方案2)constMyHelperObject={dosomething(){},};类中的静态块允许您编写一系列具有自己范围的语句,您还可以访问类中的私有字段。这意味着我们可以安心地编写初始化代码:正常编写语句,没有变量泄漏,可以完全获取类中的属性和方法。类Foo{静态#count=0;getcount(){返回Foo.#count;}static{try{constlastInstances=loadLastInstances();Foo.#count+=lastInstances.length;}catch{}}}泛型类(GenericClasses)类和接口一样,也可以写泛型。当用new实例化泛型类时,其类型参数的推断方式与函数调用相同:classBox{contents:Type;构造函数(值:类型){this.contents=value;}}constb=newBox("hello!");//constb:Box和接口一样,也可以使用通用约束和默认值。静态成员中的类型参数(TypeParametersinStaticMembers)这段代码是不合法的,但原因可能不是那么明显:classBox{staticdefaultValue:Type;//静态成员不能引用类类型参数。}存储的类型被完全擦除,在运行时,只有一个Box.defaultValue属性槽。这也意味着如果设置Box.defaultValue没问题,这也会更改Box.defaultValue,这是不好的。所以泛型类的静态成员不应该引用类的类型参数。This(thisatRuntimeinClasses)TypeScript不会改变JavaScript运行时行为,而JavaScript有时会表现出一些奇怪的运行时行为。例如,JavaScript对此的处理非常奇怪:classMyClass{name="MyClass";getName(){返回this.name;}}constc=newMyClass();constobj={name:"obj",getName:c.getName,};//打印“obj”,而不是“MyClass”console.log(obj.getName());默认情况下,函数中this的值取决于函数的调用方式。在这个例子中,由于函数是通过obj调用的,所以this的值是obj而不是类实例。这显然不是你想要的。TypeScript提供了多种方法来减轻或防止此错误。箭头函数如果你有一个函数在调用时经常丢失this上下文,那么使用箭头函数可能会更好。类MyClass{名称="MyClass";getName=()=>{returnthis.name;};}constc=newMyClass();constg=c.getName;//打印“MyClass”而不是crashingconsole.log(g());这里有几点需要注意:this的值在运行时是正确的,即使TypeScript不检查代码。这将使用更多内存,因为每个类实例都会再次复制该函数。不能在派生类中使用super.getName,因为原型链中没有入口可以获取基类方法。这个参数(thisparameters)在TypeScript方法或函数的定义中,第一个名为this的参数有着特殊的含义。此参数将在编译期间被删除://TypeScriptinputwith'this'parameterfunctionfn(this:SomeType,x:number){/*...*/}//JavaScriptoutputfunctionfn(x){/*...*/}TypeScript检查是否使用正确的上下文调用具有此参数的函数。我们可以在方法定义中添加一个this参数,而不是在前面的示例中使用箭头函数,静态地强制正确调用该方法:classMyClass{name="MyClass";getName(this:MyClass){返回this.name;}}constc=newMyClass();//OKc.getName();//错误,会崩溃constg=c.getName;console.log(g());//'void'类型的'this'上下文不能分配给'MyClass'类型的方法'this'。这个方法也有一些注意事项,恰好与箭头函数相反:JavaScript调用者仍然可以在没有意识到的情况下滥用类方法。每个类一个函数而不是每个类一个类实例、函数和基类方法定义仍然可以通过super调用。:类框{内容:字符串=“”;set(value:string){//(method)Box.set(value:string):thisthis.contents=value;归还这个;}}这里,TypeScript推断集合的返回类型是this而不是Box。让我们写一个Box的子类:classClearableBoxextendsBox{clear(){this.contents="";}}consta=newClearableBox();constb=a.set("hello");//constb:ClearableBox你也可以在参数类型注解中使用这个:classBox{content:string="";sameAs(other:this){returnother.content===this.content;}}不同于写other:Box,如果你有一个派生类,它的sameAs方法只接受来自同一个派生类的实例。类框{内容:字符串=“”;sameAs(other:this){返回其他。内容===这个。内容;}}classDerivedBoxextendsBox{otherContent:string="?";}constbase=newBox();constderived=newDerivedBox();derived.sameAs(base);//'Box'类型的参数不可赋值到“DerivedBox”类型的参数。//类型“Box”中缺少属性“otherContent”,但类型“DerivedBox”中需要属性。基于this的类型守卫(this-basedtypeguards)你可以在类和接口的方法的返回位置使用thisisType。当与类型缩小(例如,if语句)一起使用时,目标对象的类型将缩小为更具体的类型。类FileSystemObject{isFile():这是FileRep{返回这个FileRep实例;}isDirectory():这是目录{返回这个instanceof目录;}isNetworked():这是联网的&this{returnthis.networked;路径:字符串,私有网络:布尔值){}}classFileRepextendsFileSystemObject{constructor(path:string,publiccontent:string){super(path,false);}}classDirectoryextendsFileSystemObject{children:FileSystemObject[];}interfaceNetworked{host:string;}constfso:FileSystemObject=newFileRep("foo/bar.txt","foo");如果(fso.isFile()){fso.content;//constfso:FileRep}elseif(fso.isDirectory()){fso.children;//constfso:Directory}elseif(fso.isNetworked()){fso.host;//constfso:Networked&FileSystemObject}基于此的通用类型保护的示例使用是对特定字段的惰性验证。例如,在此示例中,当hasValue被验证为true时,undefined将从类型中删除:classBox{value?:T;hasValue():这是{value:T}{returnthis.value!==undefined;}}constbox=newBox();box.value="Gameboy";框值;//(property)Box.value?:unknownif(box.hasValue()){box.value;//(property)value:unknown}参数属性(ParameterProperties)TypeScript提供了一种特殊的语法,可以将构造函数的参数转换为具有相同名称和值的类属性。这些称为参数属性。可以通过在构造函数参数前添加可见性修饰符publicprivateprotected或者readonly来创建参数属性,最后这些类属性字段也会得到这些修饰符:classParams{constructor(publicreadonlyx:number,protectedy:number,privatez:number){//不需要主体}}consta=newParams(1,2,3);console.log(a.x);//(property)Params.x:numberconsole.log(a.z);//Property“z”是私有的,只能在“Params”类中访问。类表达式(ClassExpressions)类表达式和类声明非常相似,唯一的区别是类表达式不需要名字,虽然我们可以通过绑定标识符来引用:constsomeClass=class{content:Type;构造函数(值:类型){this.content=value;}};constm=newsomeClass("你好,世界");//constm:someClass抽象类和成员(abstractClassesandMembers)在TypeScript中,类、方法、字段都可以是抽象的。抽象方法或抽象字段不提供实现。这些成员必须存在于抽象类中,不能直接实例化。抽象类的作用是作为子类的基类,让子类实现所有的抽象成员。当一个类没有任何抽象成员时,它被认为是具体的。让我们看一个例子:abstractclassBase{abstractgetName():string;printName(){console.log("你好,"+this.getName());}}constb=newBase();//不能创建抽象类的实例。我们不能使用Base的新实例,因为它是抽象类。我们需要编写一个派生类并实现抽象成员。派生类扩展基础{getName(){返回“世界”;}}constd=newDerived();d.printName();请注意,如果我们忘记实现基类的抽象成员,我们将得到一个错误://什么都忘了做}抽象构造签名(AbstractConstructSignatures)有时,你想接受一个类构造函数,它可以继承一些抽象类来产生一个类的实例。例如,您可以编写如下代码:functiongreet(ctor:typeofBase){constinstance=newctor();//不能创建抽象类的实例。instance.printName();}TypeScript会报错,告诉你你正在尝试实例化一个抽象类。毕竟,根据greet的定义,这段代码应该是合法的://Bad!greet(Base);但是,如果您编写一个接受构造函数签名的函数:functiongreet(ctor:new()=>Base){constinstance=newctor();instance.printName();}greet(Derived);greet(Base);//“typeofBase”类型的参数不能分配给“new()=>Base”类型的参数。//无法将抽象构造函数类型分配给非抽象构造函数类型。现在TypeScript会正确地告诉您可以调用哪个类构造函数。Derived可以,因为它是具体的,但Base不能。类之间的关系大多数时候,TypeScript类在结构上进行比较,就像其他类型一样。例如,这两个类可以互换使用,因为它们在结构上是等价的:classPoint1{x=0;y=0;}类Point2{x=0;y=0;}//OKconstp:Point1=newPoint2();同样,即使没有明显的继承关系,也可以在类的子类型之间建立关系:classPerson{name:string;年龄:数字;}类员工{姓名:字符串;年龄:数字;salary:number;}//OKconstp:Person=newEmployee();这听起来有点简单,但有一些例子可以看出其中的怪异之处。空类没有成员。在结构化类型系统中,没有成员的类型通常是任何其他类型的超类型。所以如果你写了一个空类(举个例子,你不做),任何东西都可以用来代替它:classEmpty{}functionfn(x:Empty){//can'tdoanythingwith'x',所以我不会}//AllOK!fn(window);fn({});fn(fn);TypeScript系列TypeScript系列文章由三部分组成:官方文档的翻译,重点分析难点、实战技巧,涵盖入门、进阶、实战,旨在为您提供系统的学习TS教程,全系列预计40篇左右。点此浏览全系列文章,建议顺便收藏本站。微信:“mqyqingfeng”,加我到世优唯一的读者群。如有错误或不准确的地方,请务必指正,万分感谢。如果你喜欢或者有启发,欢迎star,这也是对作者的鼓励。