## 类(Class) uts 中使用关键字 `class` 声明类。 类声明由类名以及由花括号包围的类体构成。 ```ts // 定义Person Class class Person { } ``` ### 基本概念 类是对象化的概念,有属性、方法、构造函数。 - 属性:是一个简单的值,可以是字符串、数字、布尔或另一个class。可以用 `对象.属性名` 的访问,也可以通过 `对象.属性名=xxx` 的方式赋值。 - 方法:是一段代码的集合,有入参、有返回值(均可选)。可以用 `对象.方法名(参数)` 的方式访问。 - 构造函数:用于初始化实例。详[见下](#uts-constructor) 下面的示例中,定义了一个 Person 的 class,它有一个属性 name,有一个构造函数 constructor(名称不可改),还有一个方法 getNameLength。 ```ts // 定义Person Class class Person { name:string = ""; // 属性name constructor(newname:string) { // 构造函数,参数newname console.log("开始实例化"); this.name = newname; } getNameLength():number{ // 方法getNameLength return this.name.length } } ``` 定义了class后,需要实例化(通过new关键字)。定义一个实例后,即可使用该实例对象的属性和方法。 一个class可以被多次实例化为不同的实例,互不影响。 ```ts //实例化上面定义的class并调用其属性方法 let p = new Person("tom"); // 使用 new 关键字实例化对象时,会自动触发构造函数 console.log(p.name); // 访问p这个对象的属性name,返回值tom console.log(p.getNameLength()); // 调用p这个对象的方法getNameLength,返回值3 let p2 = new Person("jerry"); // 使用 new 关键字再实例化一个新对象 console.log(p2.name); //jerry console.log(p2.getNameLength()); //5 ``` ### 构造函数(constructor)@uts-constructor 构造函数 constructor ,在创建新对象时(new的时候)会自动执行,用于初始化对象属性。 - 语法: ```ts constructor([arguments]) { ... } ``` - 描述: 你可以不写构造函数。如果没有显式指定构造函数,运行环境会自动添加默认的 constructor 方法。 在一个类中只能有一个名为 “constructor” 的特殊方法。一个类中出现多次构造函数 (constructor)方法将会抛出一个 SyntaxError 错误。 - 示例: ```ts class Person { name:string = ""; constructor(newname:string) { this.name = newname; } } let person = new Person("tom"); // 使用 new 关键字创建对象时,会自动触发构造函数 console.log(person.name); // tom ``` 在一个构造函数中可以使用 super 关键字来调用一个父类的构造函数。这涉及继承的概念。如不了解继承可[见下](#extends) ```ts class Polygon { constructor() { this.name = "Polygon"; } } class Square extends Polygon { constructor() { super(); } } ``` > 特别说明: > iOS 平台中在实现无参的 constructor() 或者重写父类的某个有参的构造函数时需要在构造函数前加上 `@UTSiOS.override`。 示例如下: ```ts class Polygon { @UTSiOS.override constructor() { this.name = "Polygon"; } } class Square extends Polygon { @UTSiOS.override constructor() { super(); } } ``` ### 实例属性 class 有实例属性和静态属性。uts 中实例属性存在于类的每一个实例中。 #### 声明实例属性 uts 可以在类中声明属性,默认可读,可写。 ```ts class Person { name:string = ""; // 声明实例属性name city:string = "beijing" // 声明实例属性city constructor(newname:string) { this.name = newname; // 在构造函数中对name重新赋值 } } let person1 = new Person("tom"); // 使用 new 关键字创建对象时,会自动触发构造函数 console.log(person1.name); //tom console.log(person1.city); //beijing let person2 = new Person("jerry"); // 使用 new 关键字创建对象时,会自动触发构造函数 console.log(person2.name); //jerry console.log(person2.city); //beijing ``` #### Getter 与 Setter uts 支持通过 getters/setters 来截取对对象属性的访问。它可以理解为属性的读取/写入的拦截器。 下面的例子中,针对 person对象提供了name的get和set的拦截,paascode不正确时无法修改name的值。 ```ts const passcode = "secret passcode"; class Person { private _name: string = ""; // private是私有的,外部不能访问 get name(): string { // 读取name会触发此拦截器 console.log("start to get person.name"); return this._name; } set name(newName: string) { // 给name赋值会触发此拦截器 console.log("start to set person.name"); if (passcode === "secret passcode") { // 校验是否有权修改name的值,这里的条件可以修改以方便测试 this._name = newName; } else { console.log("Error: set person.name fail"); } } } let p = new Person() p.name = "tom" // 会打印"start to set person.name" console.log(p.name); // 先打印"start to get person.name",然后打印"tom" ``` #### readonly uts 可以使用 readonly 关键字将属性设置为只读的。只读属性必须在声明时或构造函数里被初始化。 ```ts class Person { readonly name: string; readonly age: number = 0; constructor (theName: string) { this.name = theName; } } let p = new Person("tom"); console.log(p.name); p.name = "jerry"; // 错误! name 是只读的 p.age = 1 // 错误! age 是只读的 ``` 但 readonly 更多是一种开发环境的语法校验。在运行时,该值往往可以改变。 ### 静态属性(static) 使用关键字 static 来将一个属性声明为静态属性。静态属性不会在实例中被调用,而只会被类本身调用。 ```ts class Person { static age:number = 10; // age是静态属性。不能在实例p中访问,但可以通过类Person访问 getAge():number{ return Person.age } } console.log(Person.age); //10 let p = new Person(); //新建一个实例 console.log(p.getAge()); //10 ``` ### 实例方法 uts 中实例方法存在于类的每一个实例中。 #### 声明实例方法 uts 可以在类中声明实例方法。 下面定义一个通过高度乘以宽度计算面积的类。 ```ts class Rectangle { private height:number; private width:number; constructor(height: number, width: number) { this.height = height; this.width = width; } calcArea(): number { return this.height * this.width; } } ``` 使用一个实例方法,以类实例调用它即可: ```ts const square = new Rectangle(10, 10); square.calcArea(); // 100 ``` ### 静态方法(static) 使用关键字 static 来将一个方法声明为静态方法。静态方法不会在实例中被调用,而只会被类本身调用。它们经常是工具函数,比如用来创建或者复制对象。 ```ts class ClassWithStaticMethod { static staticMethod(): string { return "static method has been called."; } } ClassWithStaticMethod.staticMethod(); // 不实例化,直接调用class的方法 ``` ### 继承(extends)@extends uts 允许使用继承来扩展现有的类。扩展的子类继承了父类的属性方法,但又可以添加自己独有的属性方法,以及复写父类定义的属性方法。 被继承的类称为父类(也称为超类、基类),新扩展的类称为子类(也称为派生类)。 比如定义了Person类存储人的基本信息,还可以定义一个Developer子类继承自Person类,在子类里追加Developer的独有信息。 - 语法: ```ts class ChildClass extends ParentClass { ... } ``` - 描述: extends 关键字用来创建一个类的子类。 - 示例: ```ts // 定义父类 class Person { name:string = ""; constructor(newname:string) { this.name = newname; } } // 定义子类 class Developer extends Person{ likeLanguage:string = "ts" constructor(newname:string) { super(newname) } } let d = new Developer("tom"); // 实例化。由于子类没有声明和复写自己的构造函数,所以默认继承了父类的构造函数 console.log(d.name); // tom console.log(d.likeLanguage); // ts ``` - 子类必须包含构造函数的实现且必须使用 `super` 调用父类的构造函数。 - 如果要控制父类中某些属性方法不被子类继承,可使用可见性修饰符(private、protected等),具体[见下](#modifier) - 多重继承:子类还可以被孙类继承 #### 覆盖方法(override) 覆盖,也称为复写、重写。在继承中,用于在子类中改写父类定义的方法或属性。 uts 对于可覆盖的成员以及覆盖后的成员需要显式修饰符override。 ```ts class Polygon { name(): string { return "Polygon"; } } class Square extends Polygon { constructor() { super() } override name(): string { return "Square"; } } ``` Square.name 函数上必须加上 override 修饰符。如果没写,编译器会报错。 #### 覆盖属性 属性与方法的覆盖机制相同。父类中已声明的同名属性,在子类中重新声明必须以 override 开头,并且它们必须具有兼容的类型(都是字符串、或数字、布尔值等)。 ```ts class Shape { vertexCount: Int = 0 } class Rectangle extends Shape { constructor() { super() } override vertexCount = 4 } ``` > 特别说明: > iOS 平台中不允许覆盖和父类同名的存储属性,但是可以覆盖和父类同名的计算属性。 #### 调用父类实现 子类中的代码可以使用 super 关键字调用其父类的方法。不能跨级调用父类的父类(爷爷类)的方法。 ```ts class Rectangle { draw() {} } class FilledRectangle extends Rectangle { constructor() { super() } override draw() { super.draw(); } } ``` ### 可见性修饰符@modifier 类的方法与属性都可以有可见性修饰符。 在 uts 中有三个可见性修饰符:private、 protected、 和 public。 默认可见性是 public。 #### public 在 uts 中可以自由的访问程序里定义的 public 成员,这也是 uts 的默认行为。 #### private 当成员被标记成 private 时,它就不能在声明它的类的外部访问。比如: ```ts class Person { private name: string = "Cat"; } new Person().name; // 错误: 'name' 是私有的. ``` #### protected protected 修饰符与 private 修饰符的行为很相似,但有一点不同,protected 成员在继承的派生类中仍然可以访问。比如: ```ts class Person { protected name: string; constructor(name: string) { this.name = name; } } class Employee extends Person { private department: string; constructor(name: string, department: string) { super(name); this.department = department; } public getElevatorPitch(): string { return `Hello, my name is ${this.name} and I work in ${this.department}.`; } } const howard = new Employee("Howard", "Sales"); console.log(howard.getElevatorPitch()); console.log(howard.name); // 错误 ``` 注意,我们不能在 Person 类外使用 name,但是我们仍然可以通过 Employee 类的实例方法访问,因为 Employee 是由 Person 派生而来的。 ### this `this` 只能用在成员函数(类的方法)内部,`this` 引用的是调用该函数的对象实例。`this` 的指向不受函数作用域的影响,也不可以被改变。 ```ts class Test { test() { console.log(this) // Test 的实例对象 function fn() { console.log(this) // 仍然是 Test 的实例对象 } fn() } } ``` 受目标语言的限制,编译到 kotlin/swift 时,`this` 可以缺省,此特性目前无法屏蔽,但不推荐主动使用。当类的成员命名与外层变量同名时,需注意区分。 ```ts const a = 1 class Test { a = 0 test() { console.log(a) // this.a 0 } } ``` ### 平台专有用法 #### kotlin 平台获取 Java Class 对象 [UTSAndroid.getJavaClass](utsandroid.md#getjavaclass) #### kotlin 平台 Class 特殊方法说明 在kotlin平台,class 里的某些方法是具备特殊含义的,整理记录如下: |方法名|简介|详细说明| |:-----|:--|:---| |equals|定义当前对象实例是否与另外一个对象实例相同|[文档](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-any/equals.html)| |hashcode|定义当前实例对象的散列计算过程,常用于在容器中标记当前对象实例的唯一性|[文档](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-any/hash-code.html)| |toString|以字符串的方式序列化描述当前对象实例|[文档](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-any/to-string.html)| 更多介绍参考 [文档](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-any/)