# 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()方法。")
   } 
}
  1. 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
  1. 访问控制修饰符
    • 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抽象类的使用

  1. 抽象类 继承是多态使用的前提。所以在定义很多通用的调用接口时, 通常会让调用者传入父类,通过多态来实现更加灵活的调用方式。但是,父类本身可能并不需要对某些方法进行具体的实现,所以父类中定义的方法,,可以定义为抽象方法。

抽象类有如下的特点:

  • 抽象类是不能被实例化的(也就是不能通过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 {}
  1. 类当接口

因为接口和类都定义了对象的结构,在某些情况下可以互换使用。如果需要创建一个可以自定义参数的实例,同时也可以进行类型检查,把类当做接口使用不失为一个很好的方法。

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类的多态

  1. 多态的使用前提:继承
  2. makeActions利用父类的类型
  3. 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等;

参考地址 (opens new window)

外部定义类型声明:这些库通常有两种类型声明方式:如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({
  
})
最后更新: 5/23/2024, 8:12:45 AM