前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >类_TypeScript笔记4

类_TypeScript笔记4

作者头像
ayqy贾杰
发布2019-06-12 15:07:40
6960
发布2019-06-12 15:07:40
举报
文章被收录于专栏:黯羽轻扬黯羽轻扬

一.类成员

TypeScript里的类的定义与ES6 Class规范一致,静态属性,实例属性,访问器等都支持:

代码语言:javascript
复制
class Grid {
   static origin = {x: 0, y: 0};
}class Employee {
   private _fullName: string;   get fullName(): string {
       return this._fullName;
   }   set fullName(newName: string) {
       this._fullName = newName;
   }
}

但需要注意2点:

  • ES3不支持getter/setter,因此要求编译配置为ES5+
  • 只有getter没有setter的属性会被自动推断为readonly(成员修饰符之一)

二.成员修饰符

访问控制修饰符

支持3个访问控制修饰符:

  • public:类的成员属性/方法默认都是public,没有访问限制
  • private:无法在该类声明的外部访问其成员(如无法通过this.xxx访问私有成员)
  • protected:与private类似,但在派生类中也可以访问受保护成员

例如:

代码语言:javascript
复制
class Animal {
   // 私有成员属性
   private name: string;
   // 受保护成员属性
   protected ancestor: string;
   // 不写的话,默认public
   constructor(theName: string) { this.name = theName; }
   // public成员方法
   public move(distanceInMeters: number) {
       console.log(`${this.name} moved ${distanceInMeters}m.`);
   }
}

注意,这些访问控制都只是编译时的限制,运行时并不做强检查。符合TypeScript的设计原则:

不给编译产物增加运行时开销

另外,类成员可访问性也是类型检查的一部分,private/protected修饰符会打破鸭子类型,例如:

代码语言:javascript
复制
class Animal {
   name: string;
   constructor(theName: string) { this.name = theName; }
}
class Person {
   name: string;
   constructor(theName: string) { this.name = theName; }
}
// 正确:鸭子类型(长得像就是)
let person: Animal = new Person('Sam');class Plant {
   // 私有成员name
   private name: string;
   constructor(theName: string) { this.name = theName; }
}
// 错误:name属性可访问性不匹配(长得像也不行,可访问性不一样)
// Property 'name' is private in type 'Plant' but not in type 'Person'.
let invalidPlant: Plant = new Person('Stone');

P.S.特殊的,protected constructor表示该类不允许直接实例化,但支持继承

readonly修饰符

可以通过readonly修饰符声明属性只读,只能在声明时或构造函数里赋值,例如:

代码语言:javascript
复制
class Octopus {
   readonly name: string;
   public readonly numberOfLegs: number = 8;
   constructor (theName: string) {
       this.name = theName;
   }
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // error! name is readonly.

P.S.当然,readonly与访问控制修饰符并不冲突,可以作用于同一个属性

参数属性

对于在构造函数里初始化的属性:

代码语言:javascript
复制
class Octopus {
   readonly name: string;
   constructor (theName: string) {
       this.name = theName;
   }
}

有一种简写方式

代码语言:javascript
复制
class Octopus {
   readonly numberOfLegs: number = 8;
   constructor(readonly name: string) {
   }
}

其中,name参数属性,通过给构造函数的形参名前添上private/protected/public/readonly修饰符来声明

三.继承

代码语言:javascript
复制
class A extends B {
   //...
}

类似于Babel转换(清晰起见,仅摘录关键部分):

代码语言:javascript
复制
function _inherits(subClass, superClass) {
 // 继承父类原型属性
 subClass.prototype = Object.create(superClass && superClass.prototype, {
   constructor: {
     value: subClass,
     enumerable: false,
     writable: true,
     configurable: true
   }
 });
 // 继承父类静态属性
 if (superClass)
   Object.setPrototypeOf
     ? Object.setPrototypeOf(subClass, superClass)
     : (subClass.__proto__ = superClass);
}// 子类构造函数中继承父类实例属性
function A() {
   // 通过父类构造函数给子类实例this添上父类实例属性
   return A.__proto__ || Object.getPrototypeOf(A)).apply(this, arguments)
}

TypeScript里的Class继承也会被编译替换成基于原型的继承,如下:

代码语言:javascript
复制
var __extends = (this && this.__extends) || (function () {
   var extendStatics = function (d, b) {
       extendStatics = Object.setPrototypeOf ||
           ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
           function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
       return extendStatics(d, b);
   };   return function (d, b) {
       // 继承父类静态属性
       extendStatics(d, b);
       function __() { this.constructor = d; }
       // 继承父类原型属性,及实例属性
       d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
   };
})();// __extends(A, B);

二者大同小异,从实现上看,TypeScript编译产物更健壮,因为其目标是:

在任何支持 ES3+的宿主环境中运行

P.S.比较有意思的是静态属性的继承,具体见一.如何继承静态属性?

四.抽象类

TypeScript里也有抽象类的概念:

代码语言:javascript
复制
abstract class Animal {
   abstract makeSound(): void;
   move(): void {
       console.log('roaming the earch...');
   }
}

抽象类里可以有带实现的具体方法(如move),也可以有只声明不实现的抽象方法(如makeSound),但要求子类必须实现这些方法:

代码语言:javascript
复制
class Cat extends Animal {
   makeSound() {
       console.log('meow meow meow');
   }
}

另一个相似的概念是接口,二者区别在于接口中只能定义“抽象方法”(没有abstract关键字,确切地说是方法签名),例如:

代码语言:javascript
复制
interface Animal {
 // 对比 abstract makeSound(): void;
 makeSound(): void;
}// 对比 class Cat extends Animal
class Cat implements Animal {
   makeSound() {
       console.log('meow meow meow');
   }
}

五.类与类型

声明一个类的同时,也声明了该类实例的类型,例如:

代码语言:javascript
复制
class Greeter {
   static standardGreeting = "Hello, there";
   greeting: string;
   constructor(message: string) {
       this.greeting = message;
   }
   greet() {
       return "Hello, " + this.greeting;
   }
}let greeter: Greeter;
greeter = new Greeter("world");

其中,实例greeterGreetr类型的,也就是说,Class声明具有类型含义

  • 该类实例的类型:Greeter
  • 类自身的类型:typeof Greeter

实际上,类自身的类型约束了静态属性、实例属性、构造函数、原型方法等特征,例如:

代码语言:javascript
复制
class GreeterDuck {
   // 类自身的类型约束
   static standardGreeting = 'Hi';
   greeting: string;
   constructor(message: string) { }
   greet() { return 'there'; }
   // 允许多不允许少(鸭子类型)
   sayHello() { /*...*/ }
}let greeterType: typeof Greeter = GreeterDuck;

更进一步的:

代码语言:javascript
复制
// 从Class类型扩展出Interface
interface HelloGreeter extends Greeter {
   sayHello(): void;
}let hGreeter: HelloGreeter = new GreeterDuck('world');

没错,因为类具有类型含义,所以接口能够继承自类

参考资料

  • Classes
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-01-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端向后 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.类成员
  • 二.成员修饰符
    • 访问控制修饰符
      • readonly修饰符
        • 参数属性
        • 三.继承
        • 四.抽象类
        • 五.类与类型
          • 参考资料
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档