# ts中的类
- 类的三要素:封装+多态+继承
TypeScript 类定义方式如下:
class class_name {
// 类作用域
}
class Person {
name: string //如果没有构造函数中赋值,那么需要添加默认值如 name:string= 'f1'
age: number
//构造函数不需要返回任何值,默认返回当前创建出来的实例;
constructor(name: string, age: number) {
this.name = name
this.age = age
}
eating() {
console.log(this.name + " eating")
}
}
const p = new Person("why", 18)
console.log(p.name)
console.log(p.age)
p.eating()
export {}
- 字段 − 字段是类里面声明的变量。字段表示对象的有关数据。
- 构造函数 − 类实例化时调用,可以为类的对象分配内存。
- 方法 − 方法为对象要执行的操作。
- 继承类的方法重写:其中 super 关键字是对父类的直接引用,该关键字可以引用父类的属性和方法。
class PrinterClass {
doPrint():void {
console.log("父类的 doPrint() 方法。")
}
}
class StringPrinter extends PrinterClass {
doPrint():void {
super.doPrint() // 调用父类的函数
console.log("子类的 doPrint()方法。")
}
}
- static: 关键字用于定义类的数据成员(属性和方法)为静态的,静态成员可以直接通过类名调用。
class StaticMem {
static num:number;
static disp():void {
this.num++
console.log("num 值为 "+ StaticMem.num)
}
}
StaticMem.num = 12 // 初始化静态变量
StaticMem.disp() // 调用静态方法 //num 值为 13
console.log(StaticMem.num)//13
StaticMem.num++
StaticMem.disp() // 调用静态方法 //num 值为 15
console.log(StaticMem.num)//15
- 访问控制修饰符
- public(默认) : 公有,可以在任何地方被访问。
- protected : 受保护,可以被其自身以及其子类和父类访问。
- private : 私有,只能被其定义所在的类访问。
- 通过 readonly 关键字将属性设置为只读的。只读属性必须在声明时或构造函数里被初始化。
- readonly如果定义的是对象,和js一样,可以修改里面的属性值,但是不能直接改这个对象
class Token {
readonly secret: string = 'xjx*xh3GzW#3'
readonly expired: number
constructor (expired: number) {
this.expired = expired
}
}
const token = new Token(60 * 60 * 24)
token.expired = 60 * 60 * 2 // Error, expired 是只读的
# ts类的访问器 getters setters
不可以直接访问的属性,如果有访问器,可以使用访问器去设值和读取,写法不同于方法调用。
class Person {
private _name: string
constructor(name: string) {
this._name = name
}
// 访问器setter/getter
// setter
set name(newName) {
this._name = newName
}
// getter
get name() {
return this._name
}
}
const p = new Person("why")
p.name = "coderwhy"
console.log(p.name)
export {}
# ts抽象类的使用
- 抽象类 继承是多态使用的前提。所以在定义很多通用的调用接口时, 通常会让调用者传入父类,通过多态来实现更加灵活的调用方式。但是,父类本身可能并不需要对某些方法进行具体的实现,所以父类中定义的方法,,可以定义为抽象方法。
抽象类有如下的特点:
- 抽象类是不能被实例化的(也就是不能通过new创建)
- 抽象方法必须被子类实现,否则该类必须是一个抽象类
abstract makeSound(): void; //抽象类的方法不需要函数体
抽象类作为其它派生类的基类使用,它们一般不会直接被实例化,不同于接口,抽象类可以包含成员的实现细节。 abstract 关键字是用于定义抽象类和在抽象类内部定义抽象方法。
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log('roaming the earch...');
}
}
const animal = new Animal() // Error, 无法创建抽象类实例
创建子类继承抽象类,将抽象类中的抽象方法一一实现,这样在大型项目中可以很好的约束子类的实现
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log('roaming the earch...');
}
}
class Dog extends Animal {
makeSound() {
console.log('bark bark bark...')
}
}
const dog = new Dog()
dog.makeSound() // bark bark bark...
dog.move() // roaming the earch...
- Animal中的抽象方法makeSound约束了子类中的makeSound的返回类型,如果不返回字符串,会报错
abstract class Animal {
abstract makeSound(): string;
move(): void {
console.log('roaming the earch...');
}
}
class Dog extends Animal {
makeSound() {
// 类型“Dog”中的属性“makeSound”不可分配给基类型“Animal”中的同一属性。
// 不能将类型“() => void”分配给类型“() => string”。
// 不能将类型“void”分配给类型“string”。ts(2416)
console.log('bark bark bark...')
// return '1'
}
}
# 类的类型
类本身也是可以作为一种数据类型的
class Person {
name: string = "123"
eating() {
}
}
const p = new Person()
const p1: Person = {
name: "why",
eating() {
}
}
function printPerson(p: Person) {
console.log(p.name)
}
printPerson(new Person())
printPerson({name: "kobe", eating: function() {}})
export {}
- 类当接口
因为接口和类都定义了对象的结构,在某些情况下可以互换使用。如果需要创建一个可以自定义参数的实例,同时也可以进行类型检查,把类当做接口使用不失为一个很好的方法。
class Pizza {
constructor(public name: string, public toppings: string[]) {}
}
class PizzaMaker {
// 把 Pizza 类当做接口
static create(event: Pizza) {
return new Pizza(event.name, event.toppings)
}
}
const pizza = PizzaMaker.create({
name: 'Cheese and nut pizza',
toppings: ['pasta', 'eggs', 'milk', 'cheese']
})
console.log(pizza)
class Animal {
public species: string | undefined
public weight: number | undefined
}
const simba: Animal = {
species: 'lion',
weight:100,
// speak: true // Error, 'speak' does not exist in type 'Animal'
}
# TS类的继承和重写
继承:允许创建一个类(子类),从已有的类(父类)上继承所有的属性和方法,子类可以新建父类中没有的属性和方法。
class Jspang{
public name:string
public age : number
public skill: string
constructor(name:string,age:number,skill:string){
this.name = name
this.age = age
this.skill = skill
}
public interest(){
console.log('xxx')
}
}
let jspangObj:Jspang = new Jspang('技术胖',18,'web')
jspangObj.interest()
//extends 继承
class JsShuai extends Jspang{
public xingxiang:string = '帅气'
public zhuangQian(){
console.log('一天赚了一个亿')
}
}
let shuai = new JsShuai("技术帅",5,'演讲')
shuai.interest()
shuai.zhuangQian()
extends关键字就是继承的重点.
重写就是在子类中重写父类的方法。
class JsShuai extends Jspang{
public xingxiang:string = '帅气'
// 子类重写方法
public interest(){
//如果有需要,可以继续借用父类的方法和自己的方法组合
super.interest()
console.log('建立电商平台')
}
public zhuangQian(){
console.log('一天赚了一个亿')
}
}
先是继承了父类的方法,然后通过super关键字调用了父类的方法,实现了技能的增加。
# TS类的多态
- 多态的使用前提:继承
- makeActions利用父类的类型
makeActions遍历的方法,虽然不一定要调用父类的同名方法,但是父类必须要有这个方法。
class Animal {
action() {
console.log("animal action")
}
}
class Dog extends Animal {
action() {
console.log("dog running!!!")
}
}
class Fish extends Animal {
action() {
console.log("fish swimming")
}
}
class Person extends Animal {
}
// animal: dog/fish
// 多态的目的是为了写出更加具备通用性的代码
function makeActions(animals: Animal[]) {
animals.forEach(animal => {
animal.action()
})
}
makeActions([new Dog(), new Fish(), new Person()])
# ts static应用:单例模式
把属性挂载类上而不是实例上
class Demo {
//instance是用来存储实例
static instance: Demo;
constructor(public name: string) {}
static getInstance(v) {
if (!this.instance) {
console.log('in',v)
this.instance = new Demo('dell lee');
}else{
console.log('out',v)
}
return this.instance;
}
}
console.log(Demo.instance)
const demo1 = Demo.getInstance(1);
const demo2 = Demo.getInstance(2);
console.log(demo1.name);
console.log(demo2.name);
console.log(Demo.instance)
// undefined
// in 1
// out 2
// dell lee
// dell lee
class Demo {
//instance是用来存储实例
private static instance: Demo;
private constructor(public name: string) {}
static getInstance() {
if (!this.instance) {
this.instance = new Demo('dell lee');
}
return this.instance;
}
}
const demo1 = Demo.getInstance();
const demo2 = Demo.getInstance();
console.log(demo1.name);//dell lee
console.log(demo2.name);//dell lee
//console.log(Demo.instance)//属性“instance”为私有属性,只能在类“Demo”中访问。
# TS对象
在 JavaScript 中,最基本的将数据成组和分发的方式就是通过对象。在 TypeScript 中,我们通过对象类型(object types)来描述对象。
- 对象类型可以是 匿名 的:
function greet(person: { name: string; age: number }) {
return "Hello " + person.name;
}
- 也可以使用 接口 进行定义:
interface Person {
name: string;
age: number;
}
function greet(person: Person) {
return "Hello " + person.name;
}
- 或者通过 类型别名 :
type Person = {
name: string;
age: number;
};
function greet(person: Person) {
return "Hello " + person.name;
}
- 类型模板
假如在 JavaScript 定义了一个对象:
var sites = {
site1:"Runoob",
site2:"Google"
};
这时如果想在对象中添加方法,可以做以下修改:
sites.sayHello = function(){ return "hello";}
如果在 TypeScript 中使用以上方式则会出现编译错误,因为Typescript 中的对象必须是特定类型的实例。
var sites = {
site1: "Runoob",
site2: "Google",
sayHello: function () { } // 类型模板,添加了这个才可以有后续的修改
};
sites.sayHello = function () {
console.log("hello " + sites.site1);
};
sites.sayHello();
# TS模块和命名空间
# 模块
TypeScript 与 ES6 一样,任何包含顶级 import 或者 export 的文件都被当成一个模块。相反的,如果一个文件不带有顶级的 import 或者 export 声明,那么它的内容被视为全局可见的。
- 全局模块 在同一个工程目录中,有两个文件中都命名const a = xxx;此时编译器会提示重复定义错误,虽然是在不同的文件下,但处于同一全局空间。
//如果加上 export 导出语句,两个 a 因处于不同的命名空间,就不会报错
export const a = 1
- 导出时重命名
const a: number = 1
const add = (x: number, y:number) => x + y
interface User {
nickname: string,
department: string
}
class Employee implements User {
public nickname!: string
public department!: string
}
type used = true | false
export { add }
export { a as level, used as status, Employee }
- 导入时重命名
import { a as level, used as status } from './export'
CommonJS 和 AMD 的环境里都有一个 exports 变量,这个变量包含了一个模块的所有导出内容。
CommonJS 和 AMD 的 exports 都可以被赋值为一个 对象, 这种情况下其作用就类似于 EcmaScript 2015 语法里的默认导出,即 export default 语法了。虽然作用相似,但是 export default 语法并不能兼容 CommonJS 和 AMD 的 exports。
为了支持 CommonJS 和 AMD 的 exports, TypeScript 提供了 export = 语法。
export = 语法定义一个模块的导出 对象。 这里的 对象 一词指的是类,接口,命名空间,函数或枚举。
若使用 export = 导出一个模块,则必须使用 TypeScript 的特定语法 import module = require('module') 来导入此模块。
- export = 只能导出 对象
- export = 导出的模块只能用 import = require() 形式导入
文件 ZipCodeValidator.ts:使用 export = 语法导出一个类对象。
let numberRegexp = /^[0-9]+$/
class ZipCodeValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s)
}
}
export = ZipCodeValidator
文件 Test.ts:通过 import = require() 形式导入
import Zip = require('./ZipCodeValidator')
// Some samples to try
let strings = ['Hello', '98052', '101']
// Validators to use
let validator = new Zip()
// Show whether each string passed each validator
strings.forEach(s => {
console.log(`'${ s }' - ${ validator.isAcceptable(s) ? 'matches' : 'does not match' }`)
});
# 命名空间 namespace
命名空间一个最明确的目的就是解决重名问题。命名空间本质上就是一个对象,将其内部的变量组织到这个对象的属性上.知道命名空间的使用方法即可,在 TypeScript 中一般不推荐使用。
namespace SomeNameSpaceName {
export interface ISomeInterfaceName { }
export class SomeClassName {
static reset(){
console.log(1)
}
}
}
let a=SomeNameSpaceName.SomeClassName;
console.log(a.reset())//1
如果一个命名空间在一个单独的 TypeScript 文件中,则应使用三斜杠 /// 引用它,语法格式如下:
/// <reference path = "SomeFileName.ts" />
以下实例演示了命名空间的使用,定义在不同文件中:
IShape.ts
namespace Drawing {
export interface IShape {
draw();
}
}
Circle.ts
/// <reference path = "IShape.ts" />
namespace Drawing {
export class Circle implements IShape {
public draw() {
console.log("Circle is drawn");
}
}
}
- 嵌套命名空间:命名空间支持嵌套,即你可以将命名空间定义在另外一个命名空间里头。
namespace模块化思想,可以把需要暴露的全局变量暴露出去,剩下的可以放在内部
namespace shuaiGe{
export class Dehua{
public name:string = '刘德华'
talk(){
console.log('我是帅哥刘德华')
}
}
}
namespace bajie{
export class Dehua{
public name:string = '马德华'
talk(){
console.log('我是二师兄马德华')
}
}
}
let dehua1:shuaiGe.Dehua = new shuaiGe.Dehua()
let dehua2:shuaiGe.Dehua = new bajie.Dehua()
dehua1.talk()
namespace Home {
class Header {
constructor() {
const elem = document.createElement('div');
elem.innerText = 'This is Header';
document.body.appendChild(elem);
}
}
class Content {
constructor() {
const elem = document.createElement('div');
elem.innerText = 'This is Content';
document.body.appendChild(elem);
}
}
class Footer {
constructor() {
const elem = document.createElement('div');
elem.innerText = 'This is Footer';
document.body.appendChild(elem);
}
}
export class Page {
constructor() {
new Header();
new Content();
new Footer();
}
}
}
可以把某些代码拆分出来,同时需要修改"module": "amd", "outFile": "./dist/page.js" 等配置
//提示是和Components有关联
///<reference path="components.ts" />
namespace Home {
export namespace Dell {
export const teacher: Components.user = {
name: 'dell'
};
}
export class Page {
constructor() {
new Components.Header();
new Components.Content();
new Components.Footer();
new Components.Footer();
}
}
}
namespace Components {
export interface user {
name: string;
}
export class Header {
constructor() {
const elem = document.createElement('div');
elem.innerText = 'This is Header';
document.body.appendChild(elem);
}
}
export class Content {
constructor() {
const elem = document.createElement('div');
elem.innerText = 'This is Content';
document.body.appendChild(elem);
}
}
export class Footer {
constructor() {
const elem = document.createElement('div');
elem.innerText = 'This is Footer';
document.body.appendChild(elem);
}
}
}
还可以使用requirejs来实现配合import的模式来使用,在html中引入requirejs,使用define(['xxx'],function(xx){new xx.default()}),而原本的namespace Commponents不需要命名空间,直接在Page中import导入即可
//在别的文件使用到命名空间内容,需要导出命名控件
export namespace time {
export function format(time: string) {
return "2222-02-22"
}
export function foo() {
}
//如果不加export,那么只能在模块内部使用
export let name: string = "abc"
}
console.log(time.foo)
import { time, price } from './utils/format'
console.log(time.format("11111111"))
# ts声明文件 declare (类型查找)
ts在开发过程中不可避免要引用第三方 JS 库。虽然通过直接引用可以调用库的类和方法,但是却无法使用TypeScript 诸如类型检查等特性功能。
需要将这些库里的函数和方法体去掉后只保留导出类型声明,而产生了一个描述 JS库和模块信息的声明文件。通过引用这个声明文件,就可以借用 TypeScript 的各种特性来使用库文件了。
想使用第三方库,比如 jQuery,通常这样获取一个 id 是 foo 的元素:
$('#foo');
// 或
jQuery('#foo');
但是在 TypeScript 中,并不知道 $ 或 jQuery 是什么东西:
jQuery('#foo');
// index.ts(1,1): error TS2304: Cannot find name 'jQuery'.
这时,需要使用 declare 关键字来定义它的类型,帮助 TypeScript 判断传入的参数类型对不对:
declare var jQuery: (selector: string) => any;
jQuery('#foo');
declare 定义的类型只会用于编译时的检查,编译结果中会被删除。
上例的编译结果是:
jQuery('#foo');
声明文件以 .d.ts 为后缀 声明文件或模块的语法格式如下:
declare module Module_Name {
}
TypeScript 引入声明文件语法格式:
/// <reference path = " runoob.d.ts" />
# typescript在哪里查找类型声明
- 内置类型声明;
- 外部定义类型声明;
- 自己定义类型声明;
内置类型声明是typescript自带的、帮助我们内置了JavaScript运行时的一些标准化API的声明文件;包括比如Math、Date等内置类型,也包括DOM API,比如Window、Document等;
外部定义类型声明:这些库通常有两种类型声明方式:如axios
- 方式一:在自己库中进行类型声明(编写.d.ts文件),比如axios
- 方式二:通过社区的一个公有库DefinitelyTyped存放类型声明文件
该库的GitHub地址 (opens new window) 该库的使用方式 (opens new window)
什么情况下需要自己来定义声明文件呢?
- 情况一:使用的第三方库是一个纯的JavaScript库,没有对应的声明文件;比如lodash
- 情况二:给自己的代码中声明一些类型,方便在其他地方直接进行使用;
xxx.d.ts文件,会自动读取
// 声明模块
declare module 'lodash' {
export function join(arr: any[]): void
}
// 声明变量/函数/类
declare let whyName: string
declare let whyAge: number
declare let whyHeight: number
declare function whyFoo(): void
declare class Person {
name: string
age: number
constructor(name: string, age: number)
}
// 声明文件
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.png'
declare module '*.svg'
declare module '*.gif'
// 声明命名空间
declare namespace $ {
export function ajax(settings: any): any
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js
"></script>
<script>
//这部分打包后的文件应该在main.ts之前,但是直接在main.ts中使用会报错,需要配置上述的.d.ts文件
let whyName = "coderwhy"
let whyAge = 18
let whyHeight = 1.88
function whyFoo() {
console.log("whyFoo")
}
function Person(name, age) {
this.name = name
this.age = age
}
</script>
</body>
</html>
main.ts
import nhltImage from './img/nhlt.jpg'
import axios from 'axios'
import lodash from 'lodash'
// axios.get("http://123.207.32.32:8000/home/multidata").then(res => {
// console.log(res)
// })
console.log(whyName)
console.log(whyAge)
console.log(whyHeight)
whyFoo()
const p = new Person("why", 18)
console.log(p)
$.ajax({
})