这是我参与更文挑战的第 19 天,活动详情查看: 更文挑战
基础小咸菜,JS/TS类
1 类在JS中的表现(ES6)
先上代码:
class Test {
constructor( params ){
// 实列成员
this.name = 'name';
this.params = params;
this.testAction = ()=>{
console.log('构造函数上的方法')
}
}
// 原型方法,定义在原型对象上
testPublicAction(){
console.log('我是公共方法')
}
// 静态方法 定义在类本身上
static testStaticAction (){
console.log('我是静态方法')
}
}
// es6中没有静态属性,因此添加静态属性可以这么添加
Test.myattr = "类的静态属性";
// 在原型上定义数据成员
Test.prototype.firistname = "在原型上定义数据成员";
const testone = new Test('我是参数');
console.log(testone.name);
console.log(testone.params);
console.log(testone.firistname); // 打印 在原型上定义数据成员
testone.testPublicAction();
// testone.testStaticAction();// 静态方法放实例中调用报错
Test.testStaticAction();// 正确
testone.testAction();
class TestTwo extends Test{
constructor(paramsOne,paramsTwo){
super(paramsOne); // 调用继承Test类的构造函数
this.testTwoName = paramsTwo;
this.testTwoAction = ()=>{
console.log('我是类testTwo上的方法')
}
}
// 可以在静态方法中通过super调用继承的类上定义的静态方法
static handExtendsParentAction (){
super.testStaticAction();
}
}
const testtow = new TestTwo('我是一个新参数一','我是一个新参数二');
console.log(testtow)
testtow.testPublicAction();
TestTwo.handExtendsParentAction();
复制代码
1.1 在es6类中的构造函数
constructor
会告诉解释器在使用new
操作符创建新的实例时调用这个2.函数。
1.2 实例化
使用new
调用类的构造函数会执行如下操作
- 在内存中创建一个新的对象
- 在这个新对象内部的__proto__指针被赋值为构造函数的prototype属性
- this指向新对象
- 指向构造函数的代码
- 如果构造函数返回非控对象这返回该对象;如果为空则返回刚创建的对象
补充:其实类也是一种特殊的函数
2. 继承
es6实现继承比使用原型链实现继承简单多了,直接使用extends关键字即可
2.1 super()
派生类使用super关键字引用他们的原型。
- 在静态方法中可以通过
super()
调用继承类上的定义的静态方法 super()
只能在派生类构造函数和静态方法中使用- 不能单独引用
super()
关键字,要么用他调用构造函数,要么用它引用静态方法 - 调用
super()
会调用父类构造函数,并将返回的实例赋值给this super(
)的行为如同调用构造函数,如果需要给父类构造函数传参,则需要手动传入- 如果没有定义类构造函数,在实例化派生类会调用super而且会传入所有传给派生类的参数
- 在类的构造函数中不能再调用
super()
之前调用this - 如果在派生类中显示的定义了构造函数,则要么必须在其中调用super(),要么必须在其中返回一个对象
2.2 抽象基类
有时候需要这样一个类:提供给其他类继承,但本身不会被实例化
实现如下:
class BaseClass {
constructor(){
if(new.target === BaseClass){
throw new Error('这是一个抽象基类')
}
}
}
复制代码
3 ts的类
其实ts的类和es6都差不多,多了如下:
3.1 public
public
表示公共的,用来指定在创建实例后可以通过实例访问的,也就是类定义的外部可以访问的属性和方法。默认是 public
,但是 TSLint
可能会要求你必须用修饰符来表明这个属性或方法是什么类型的。
class Point {
public x: number;
public y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
public getPosition() {
return `(${this.x}, ${this.y})`;
}
}
复制代码
3.2 private
private修饰符表示私有的,它修饰的属性在类的定义外面是没法访问的:
class Parent {
private age: number;
constructor(age: number) {
this.age = age;
}
}
const p = new Parent(18);
console.log(p); // { age: 18 }
console.log(p.age); // error 属性“age”为私有属性,只能在类“Parent”中访问
console.log(Parent.age); // error 类型“typeof ParentA”上不存在属性“age”
class Child extends Parent {
constructor(age: number) {
super(age);
console.log(super.age); //error 通过 "super" 关键字只能访问基类的公共方法和受保护方法
}
}
复制代码
3.3 protected
protected修饰符是受保护修饰符,和private有些相似,但有一点不同,protected修饰的成员在继承该类的子类中可以访问,上面那个例子,把父类 Parent 的 age 属性的修饰符 private 替换为 protected:
class Parent {
protected age: number;
constructor(age: number) {
this.age = age;
}
protected getAge() {
return this.age;
}
}
const p = new Parent(18);
console.log(p.age); // error 属性“age”为私有属性,只能在类“ParentA”中访问
console.log(Parent.age); // error 类型“typeof ParentA”上不存在属性“age”
class Child extends Parent {
constructor(age: number) {
super(age);
console.log(super.age); // undefined
console.log(super.getAge());
}
}
new Child(18)
复制代码
protected
还能用来修饰 constructor
构造函数,加了protected
修饰符之后,这个类就不能再用来创建实例,只能被子类继承,这个需求 ES6 的类的时候需要用new.target
来自行判断,而 TS 则只需用 protected
修饰符即可
3.4 readonly
修饰符
在类里可以使用readonly关键字将属性设置为只读
class UserInfo {
readonly name: string;
constructor(name: string) {
this.name = name;
}
}
const user = new UserInfo("Lison");
user.name = "haha"; // error Cannot assign to 'name' because it is a read-only property
复制代码
设置为只读的属性,实例只能读取这个属性值,但不能修改。
3.5 存取器
这个也就 ES6 标准中的存值函数和取值函数,也就是在设置属性值的时候调用的函数,和在访问属性值的时候调用的函数,用法和写法和 ES6 的没有区别:
class UserInfo {
private _fullName: string;
constructor() {}
get fullName() {
return this._fullName;
}
set fullName(value) {
console.log(`setter: ${value}`);
this._fullName = value;
}
}
const user = new UserInfo();
user.fullName = "Lison Li"; // "setter: Lison Li"
console.log(user.fullName); // "Lison Li"
复制代码
3.6 抽象类
抽象类一般用来被其他类继承,而不直接用它创建实例。抽象类和类内部定义抽象方法,使用abstract关键字
3.7 类类型接口
使用接口可以强制一个类的定义必须包含某些内容,先来看个例子:
interface FoodInterface {
type: string;
}
class FoodClass implements FoodInterface {
// error Property 'type' is missing in type 'FoodClass' but required in type 'FoodInterface'
static type: string;
constructor() {}
}
复制代码
上面接口 FoodInterface
要求使用该接口的值必须有一个 type
属性,定义的类FoodClass
要使用接口,需要使用关键字implements
。implements
关键字用来指定一个类要继承的接口,如果是接口和接口、类和类直接的继承,使用extends
,如果是类继承接口,则用implements
。
有一点需要注意,接口检测的是使用该接口定义的类创建的实例,所以上面例子中虽然定义了静态属性 type
,但静态属性不会添加到实例上,所以还是报错,所以我们可以这样改:
interface FoodInterface {
type: string;
}
class FoodClass implements FoodInterface {
constructor(public type: string) {}
}
复制代码
当然这个需求抽象类实现:
abstract class FoodAbstractClass {
abstract type: string;
}
class Food extends FoodAbstractClass {
constructor(public type: string) {
super();
}
}
复制代码
3.8 接口继承类
接口可以继承一个类,当接口继承了该类后,会继承类的成员,但是不包括其实现,也就是只继承成员以及成员类型。接口还会继承类的private
和protected
修饰的成员,当接口继承的这个类中包含这两个修饰符修饰的成员时,这个接口只可被这个类或他的子类实现。
class A {
protected name: string;
}
interface I extends A {}
class B implements I {} // error Property 'name' is missing in type 'B' but required in type 'I'
class C implements I {
// error 属性“name”受保护,但类型“C”并不是从“A”派生的类
name: string;
}
class D extends A implements I {
getName() {
return this.name;
}
}
复制代码
3.9 在泛型中使用类类型
这里我们先来看个例子:
const create = <T>(c: { new (): T }): T => {
return new c();
};
class Info {
age: number;
}
create(Info).age;
create(Info).name; // error 类型“Info”上不存在属性“name”
复制代码
在这个例子里,我们创建了一个一个 create 函数,传入的参数是一个类,返回的是一个类创建的实例,这里有几个点要讲:
参数c
的类型定义中,new()
代表调用类的构造函数,他的类型也就是类创建实例后的实例的类型。
return new c()
这里使用传进来的类 c
创建一个实例并返回,返回的实例类型也就是函数的返回值类型。
所以通过这个定义,TS
就知道,调用 create
函数,传入的和返回的值都应该是同一个类类型。