[TypeScript]Day02—TypeScript函数、类型断言、对象、类、接口

八、TS 函数

8.1 函数的定义

函数又称为方法,用函数来封装相似的代码,实现代码复用,提升开发效率。

var aarr:number[]=[1,2,3,8]

//声明函数
function Sum(arr:number[]) {
    let sum :number =0
    for (let i = 0; i < arr.length; i++) {
        sum+=arr[i]
    }
    console.log(sum);
}
//调用函数
Sum(aarr)

ts可以先调用再声明函数

 fn()
 function fn(){ console.log('1111')} 

8.2 形参和实参

形参和实参

  • 形参,指定函数可接收数据类型,形参有多个,用逗号分隔
  • 实参,具体的值,在调用函数时传入给形参
//arr:number[] 为形参
function Sum(arr:number[]) {}
//arr1数组为实参
Sum(arr1)

8.3 函数返回值

函数返回值:将函数内部结果返回,以便下一步操作计算

  1. 指定返回值类型 function fn( ):类型注解 {}
  2. 指定返回值 return 返回值
function getName(name:string):string {
	return name
}

如果未指定函数返回值,那么函数返回值的默认类型为void(空,啥也没有)

注意:使用变量接收返回值时,变量类型要与返回值的类型一致

function fn():number{
	return 18
}

let res:number=18
console.log(res)

8.4 变量作用域

变量作用域:指定了变量定义的位置,变量的可用性由变量作用域决定。

TypeScript 有以下几种作用域:

  • 全局作用域 − 全局变量定义在程序结构的外部,可以在你代码的任何位置使用

  • 类作用域 −类变量声明在一个类里头,但在类的方法外面。 该变量可以通过类的对象来访问。类变量也可以是静态的,静态的变量可以通过类名直接访问。

  • 局部作用域 − 在函数内部声明的,局部变量只能在声明它的一个代码块(如:方法)中使用。

以下实例说明了三种作用域的使用:

var global_num = 12          // 全局变量
class Numbers { 
   num_val = 13;             // 实例变量
   static sval = 10;         // 静态变量
   
   storeNum():void { 
      var local_num = 14;    // 局部变量
   } 
} 
console.log("全局变量为: "+global_num)  
console.log(Numbers.sval)   // 静态变量
var obj = new Numbers(); 
console.log("实例变量: "+obj.num_val)

8.5 可选参数、默认参数、剩余参数

1. 可选参数

可选参数:可选参数使用问号?标识 ,可选参数必须跟在必需参数后面

//firstName和lastName必传
function buildName(firstName: string, lastName: string) {
    return firstName + " " + lastName;
}  
let result1 = buildName("Bob");                  // 错误,缺少参数
let result2 = buildName("Bob", "Adams", "Sr.");  // 错误,参数太多了
let result3 = buildName("Bob", "Adams");         // 正确 
 //lastName 为可选参数
function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}
 
let result1 = buildName("Bob");  // 正确
let result2 = buildName("Bob", "Adams", "Sr.");  // 错误,参数太多了
let result3 = buildName("Bob", "Adams");  // 正确 

如果想让 firstName 是可选的,lastName 必选,那么就要调整它们的位置,把 firstName 放在后面,如果都是可选参数就没关系。

2. 默认参数

默认参数:调用函数如果不传入该参数的值,则使用默认参数。

语法如下:

function fn(firstName: string, lastName: string = 'John'){ }

注意:参数不能同时设置为可选和默认。

3. 剩余参数

剩余参数:无法确定参数数量,可以用剩余参数来定义。
剩余参数允许我们将一个不确定数量的参数作为一个数组传入。

function buildName(firstName: string, ...restOfName: string[]) {
    return firstName + " " + restOfName.join(" ");
}
  
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
function addNumbers(...nums:number[]) {  
    var i;   
    var sum:number = 0; 
    
    for(i = 0;i<nums.length;i++) { 
       sum = sum + nums[i]; 
    } 
    console.log("和为:",sum) 
 } 
 addNumbers(1,2,3) 
 addNumbers(10,10,10,10,10)

8.6 匿名函数

匿名函数:一个没有函数名的函数
函数表达式:将匿名函数赋值给一个变量

var msg = function() { 
    return "hello world";  
} 
console.log(msg())

var res = function(a:number,b:number) { 
    return a*b;  
}; 
console.log(res(12,2))

匿名函数自调用

(function () { 
    var x = "Hello!!";   
    console.log(x)     
 })()

8.7 构造函数

TypeScript 也支持使用 JavaScript 内置的构造函数 Function() 来定义函数:

var myFunction = new Function("a", "b", "return a * b"); 
var x = myFunction(4, 3); 
console.log(x);

8.8 箭头函数

箭头函数: ( [param1, parma2,…param n] )=>statement;

var foo = (x:number)=>10 + x 
console.log(foo(100))      //输出结果为 110

8.9 函数重载

函数重载:允许一个函数接受不同数量或类型的参数时,作出不同的处理。

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

上例中,我们重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现。

注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面

九、类型断言

类型断言(Type Assertion):可以用来手动指定一个值的类型

9.1 语法

  • 值 as 类型
  • <类型>值
let bord = document.querySelector('#bord') as HTMLDivElement
// 或者这么用
let bord = <HTMLDivElement> document.querySelector('#bord')   

const date = dealDate(dateFormatter('2020-7-28','-') as string); 
// 或者这么用
const a = dealDate(<string>dateFormatter('2020-7-28','-')); 

9.2 用法

原文:将一个联合类型断言为其中一个类型

1. 联合类型断言其一类型

TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型中共有的属性或方法(这里是 name 属性):

interface Cat {
    name: string;
    run(): void;
}

interface Fish {
    name: string;
    swim(): void;
}

function getName(animal: Cat | Fish) {
    return animal.name;
}

而有时候,我们确实需要在还不确定类型的时候就访问其中一个类型特有的属性或方法(这里访问swim()函数)会报错,比如:

interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}

function isFish(animal: Cat | Fish) {
    if (typeof animal.swim === 'function') {
        return true;
    }
    return false;
}

// index.ts:11:23 - error TS2339: Property 'swim' does not exist on type 'Cat | Fish'. 
// Property 'swim' does not exist on type 'Cat'.

上面的例子中,获取 animal.swim 的时候会报错。忽略了 可能为 Cat 的情况,将animal直接断言为 Fish ,,而 TypeScript 编译器信任了我们的断言,故在调用swim()时没有编译错误。

可是swim()接受的参数是 Cat | Fish,一旦传入的参数是 Cat 类型的变量,由于 Cat 上没有 swim(),就会导致运行时错误了

总之,使用类型断言时一定要格外小心,尽量避免断言后调用方法或引用深层属性,以减少不必要的运行时错误。

2. 父类断言为具体子类

class ApiError extends Error {
    code: number = 0;
}
class HttpError extends Error {
    statusCode: number = 200;
}

function isApiError(error: Error) {
    if (typeof (error as ApiError).code === 'number') {
        return true;
    }
    return false;
}

由于父类 Error 中没有 code 属性,故直接获取error.code会报错,需要使用类型断言获取 (error as ApiError).code

3. 任何类型断言为any

当我们引用一个在此类型上不存在的属性或方法时,就会报错:

const foo: number = 1;
foo.length = 1;

// index.ts:2:5 - error TS2339: Property 'length' does not exist on type 'number'.
//数字类型的变量 foo 上是没有 length 属性的
window.foo = 1; 
// index.ts:1:8 - error TS2339: Property 'foo' does not exist on type 'Window & typeof globalThis'.

上面的例子中,我们需要将 window 上添加一个属性 foo,但 TypeScript 编译时会报错,提示我们 window 上不存在 foo 属性。

此时我们可以使用 as any 临时将 window 断言为 any 类型:

(window as any).foo = 1;

在 any 类型的变量上,访问任何属性都是允许的。

需要注意的是,将一个变量断言为 any 可以说是解决 TypeScript 中类型问题的最后一个手段

它极有可能掩盖了真正的类型错误,所以如果不是非常确定,就不要使用 as any

4. any断言为具体类型

function getCacheData(key: string): any {
    return (window as any).cache[key];
}

那么我们在使用它时,最好能够将调用了它之后的返回值断言成一个精确的类型,这样就方便了后续的操作:

function getCacheData(key: string): any {
    return (window as any).cache[key];
}

interface Cat {
    name: string;
    run(): void;
}

const tom = getCacheData('tom') as Cat;
tom.run();

上面的例子中,我们调用完 getCacheData 之后,立即将它断言为 Cat 类型。这样的话明确了 tom 的类型,后续对 tom 的访问时就有了代码补全,提高了代码的可维护性。

十、TS 对象

10.1 对象的含义

对象:一组相关属性和方法的集合,并且是无序的。 对象的属性或方法采用键值对形式

var obj={
	key1:value1,
	key2:value2
}
var sites = {
    site1: "Runoob",
    site2: "Google",
    sayHello: function () { } // 类型模板
};

10.2 对象的类型注解

TS中的对象是结构化的,使用前设计好对象的结构,对象的类型注解约束对象的结构

//1. 此处的{}表示对象的类型注解
let person : {
	name:string,
	age:number
}
//2. 此处的{}表示对象的定义
person = {
	name:'hh',
	age:18
}

10.3 对象方法的类型注解

给对象中的方法添加类型注解

let obj:{
	 name:string,
	 sayHi:(name:string)=>void,  //无返回
	 sing:(song:string)=>void,   //无返回
	 sum:(num1:number,num2:number)=>number   //返回类型为number
}

obj={
	name:'hh',
	sayHi:function(name:string){
		console.log("hello!"+name)
	},
	sing:(song:string)=>{ 
		console.log("唱歌"+song)
	},
	sum:(num1:number,num2:number)=>{
		return num1+num2
	}
} 

10.4 对象取值、存值

//取值
console.log(obj.name)  //'hh'

//调用sayHi方法
obj.sayHi('TT')          //"hello!TT"

//存值
obj.name='安安'  //类型要一致

10.5 内置对象

内置对象:根据标准在全局作用域(Global)上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM)的标准。

1. ECMAScript内置对象

ECMAScript 标准提供的内置对象有:

Boolean、Error、Date、RegExp 等。

我们可以在 TypeScript 中将变量定义为这些类型:

let b: Boolean = new Boolean(1); //true
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/; 

2. DOM 和 BOM 的内置对象

DOM 和 BOM 提供的内置对象有:

Document、HTMLElement、Event、NodeList 等。

TypeScript 中会经常用到这些类型:

let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
  // Do something
});

十一、TS 类

11.1 类的含义

类(Class):定义了一件事物的抽象特点,包含它的属性和方法。

ES6中类的用法

  • 使用 class 定义类,使用 constructor 定义构造函数。

  • 通过 new 生成新实例的时候,会自动调用构造函数。

class Animal {
    public name;
    constructor(name) {
        this.name = name;
    }
    sayHi() {
        return `My name is ${this.name}`;
    }
}

var q = new Animal('Jack');
console.log('q.sayHi()',q.sayHi()); // My name is Jack

11.2 类的继承

使用 extends 关键字实现继承,子类中使用super 关键字来调用父类的构造函数和方法。

class Cat extends Animal {
  constructor(name) {
    super(name); // 调用父类的 constructor(name)
  }
  sayHi() {
    return 'Cat, ' + super.sayHi(); // 调用父类的 sayHi()
  }
}

let c = new Cat('Tom'); // Tom
console.log('c.sayHi()',c.sayHi()); // Meow, My name is Tom

11.3 存取器

使用 getter 和 setter 可以改变属性的赋值和读取行为

class Person{
	constructor(name){
		this.name=name
	}
	get name(){
		return 'Jack'
	}
	set name(value){
		// console.log('setter:'+value)
	}
}

let a = new Animal('Kitty'); // setter: Kitty
a.name = 'Tom'; // setter: Tom
console.log('a.name',a.name); // Tom

11.4 静态修饰符

静态方法

使用static 修饰符修饰的方法称为静态方法,他们不需要实例化,而是直接通过类来调用

静态属性

使用static 修饰符修饰的属性称为静态属性

class Fruit{
 	static num=1  // 静态属性
	static isFruit(){  //静态方法
		return a instanceof Fruit
	}
}
let apple =new Fruit('apple');
console.log(Fruit.isFruit(apple))  //true
console.log(Fruit.num) //1  

实例属性

直接在类里面定义的属性,用实例化对象进行访问


class Fruit {
  public name 
  isSale= false
  constructor(name) {
    this.name = name;
  }
	static isFruit(a){
		return a instanceof Fruit
	}
}
let apple =new Fruit('apple'); 
console.log(apple.isSale) //false

11.5 TS中类的使用

TypeScript 可以使用三种访问修饰符,分别是 publicprivateprotected

  • public 公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
  • private 私有的,只能被其定义所在的类访问。
  • protected 受保护的,可以被其自身以及其子类和父类访问。
class Encapsulate {
  public str1:string = "hello"
  private str2:string = "world"

  public constructor(name) {
    console.log(name)
  }

  protected sum(num1:number,num2:number):number{
    return num1+num2
  }

  toString():string {
    return "111" + this.sum(1,2)
  }

}

var obj:Encapsulate = new Encapsulate('hh')
obj.sum(1,2)  //属性“sum”受保护,只能在类“Encapsulate”及其子类中访问。
console.log(obj.str1)     // 可访问
console.log(obj.str2)   // 编译错误, 属性“str2”为private私有属性,只能在类“Encapsulate”中访问
console.log(obj.toString())

子类 继承 Encapsulate

class encapsulate extends Encapsulate{ 
  private name='大猪头'
  constructor (name){
    super(name)  // 调用父类的constructor(name) 
  } 
  toString():string {// 子类方法与父类方法同名时,会覆盖父类的方法 
    return ' '+this.name + super.toString()+super.sum(5,6); // 调用父类的toString() 和sum方法
  } 
}
var obj1:encapsulate = new encapsulate('jj') 
console.log(obj1.toString())

readonly

只读属性关键字,只允许出现在属性声明或索引签名或构造函数中。

class W{
  readonly name;
  public constructor(name) {
    this.name = name;
  }
}

let b = new W('Jack');
console.log('b.name',b.name); // Jack
// b.name = 'Tom'; //Cannot assign to 'name' because it is a read-only

11.6 抽象类

abstract 用于定义抽象类和其中的抽象方法

  • 抽象类是不允许被实例化的
  • 抽象类中的抽象方法必须被子类实现
abstract class MyClass {
  public name: string;
  public constructor(name:string) {
    this.name = name;
  }
  public abstract sayHi(): string; //抽象类必须被子类实现
}

class myclass extends MyClass {
  public sayHi(): string {  //实现抽象类
    let msg:string=`Hi, My name is ${this.name}`
    return msg
  }
}

let tom = new myclass('Tom');
console.log(tom.sayHi())

十二、TS 接口

12.1 接口的含义

接口:为对象的类型注解命名,约束对象的结构

interface 表示接口,接口名称约定以I开头,推荐使用接口作为对象的类型注解

接口的作用:

  • 约束对象的类型
  • 对类的一部分行为进行抽象

约束对象的类型

interface IUser{ 
	readonly id:number, //只读,不可修改
	name:string,
	age?:number, //可选
	sayHi:()=> string 
}  

使用接口

let p:IUser={
	name:'hh',
	age:18,
	sayHi:():string =>{return "Hi there"} 
}

类与接口

接口除了为对象的类型注解命名,约束对象的结构,还可以对类的一部分行为进行抽象 。

一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,把特性提取成接口,用 implements 关键字来实现。

门是一个类,防盗门是门的子类。如果防盗门有一个报警器的功能,我们可以简单的给防盗门添加一个报警方法。这时候如果有另一个类,车,也有报警器的功能,就可以考虑把报警器提取出来,作为一个接口,防盗门和车都去实现它:

interface Alarm {  //报警器
    alert(): void;   //报警方法
}

class Door { //
}

class SecurityDoor extends Door implements Alarm {  
// 防盗门是门的子类,有报警器的功能
    alert() {
        console.log('SecurityDoor alert');
    }
}

class Car implements Alarm {
// 车,也有报警器的功能
    alert() {
        console.log('Car alert');
    }
}

一个类可以实现多个接口:

interface Alarm {
    alert(): void;
}

interface Light {
    lightOn(): void;
    lightOff(): void;
}

class Car implements Alarm, Light {
    alert() {
        console.log('Car alert');
    }
    lightOn() {
        console.log('Car light on');
    }
    lightOff() {
        console.log('Car light off');
    }
}

上例中,Car 实现了 Alarm 和 Light 接口,既能报警,也能开关车灯。

12.2 接口继承接口

接口继承:继承使用关键字 extends,TS允许接口继承多个接口,接口可以通过其他接口来扩展自己。

//单继承 
interface Person { 
   age:number 
} 
 
interface Musician extends Person { 
   instrument:string 
} 
 
var drummer = <Musician>{}; 
drummer.age = 27 
drummer.instrument = "Drums" 
console.log("年龄:  "+drummer.age)
console.log("喜欢的乐器:  "+drummer.instrument)
//多继承
interface IParent1 { 
    v1:number 
} 
 
interface IParent2 { 
    v2:number 
} 
 
interface Child extends IParent1, IParent2 { } 
var Iobj:Child = { v1:12, v2:23} 
console.log("value 1: "+Iobj.v1+" value 2: "+Iobj.v2)

12.3 接口继承类

class Point {
    x: number;
    y: number;
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
}

interface PointInstanceType {
    x: number;
    y: number;
}

// 等价于 interface Point3d extends PointInstanceType
interface Point3d extends Point {
    z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3};

当我们声明 interface Point3d extends Point 时,Point3d 继承的实际上是类 Point实例的类型

换句话说,可以理解为定义了一个接口 Point3d 继承另一个接口 PointInstanceType, 所以「接口继承类」和「接口继承接口」没有什么本质的区别。。

我们可以把Point 类当做一种名为 Point 的类型(实例的类型)来看待

所以我们既可以将 Point 当做一个类来用(使用 new Point 创建它的实例),也可以将 Point 当做一个类型来用(使用 : Point 表示参数的类型):

class Point {
    x: number;
    y: number;
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
}

// 1.   Point 当做一个类来用(使用 new Point 创建它的实例)
const p = new Point(1, 2);


// 2.   Point 当做一个类型来用(使用 : Point 表示参数的类型)
function printPoint(p: Point) {
    console.log(p.x, p.y);
}

printPoint(new Point(1, 2));


//  2 可以看做下面代码
interface PointInstanceType {
    x: number;
    y: number;
}

function printPoint(p: PointInstanceType) {
    console.log(p.x, p.y);
}

printPoint(new Point(1, 2));

我们新声明的 PointInstanceType 类型,与声明 class Point 时创建的 Point 类型是等价的。

另外需要注意的是

PointInstanceType 相比于 Point,缺少了 constructor 方法,这是因为声明 Point 类时创建的 Point 类型是不包含构造函数的。另外,除了构造函数是不包含的,静态属性或静态方法也是不包含的(实例的类型当然不应该包括构造函数、静态属性或静态方法)。

换句话说,声明 Point 类时创建的 Point 类型只包含其中的实例属性实例方法

class Point {
    /** 静态属性,坐标系原点 */
    static origin = new Point(0, 0);
    /** 静态方法,计算与原点距离 */
    static distanceToOrigin(p: Point) {
        return Math.sqrt(p.x * p.x + p.y * p.y);
    }
    
    /** 实例属性,x 轴的值 */
    x: number;
    /** 实例属性,y 轴的值 */
    y: number;

    /** 构造函数 */
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
    
    /** 实例方法,打印此点 */
    printPoint() {
        console.log(this.x, this.y);
    }
}

interface PointInstanceType {
    x: number;
    y: number;
    printPoint(): void;
}

let p1: Point;
let p2: PointInstanceType;

同样的,在接口继承类的时候,也只会继承它的实例属性和实例方法。

12.4 type 与 interface 的区别

相同点

  • 都可以描述一个对象或者函数
  • 都允许继承(extends)
都可以描述一个对象或者函数
interface User {
  name: string
  age: number
}

interface SetUser {
  (name: string, age: number): void;
}
type User = { 
	name: string
	age: number
}

type setUser = (name: string, age:number) => void;
都允许继承(extends)

相互继承,语法有所不同

interface extends interface

interface Name {
   name: string;
}

interface User extends Name {
	age: number;
}

interface extends type

type Name = {
	name: string;
}

interface User extends Name {
	age: number
}

type extends type

type Name = {
	name: string;
}

type User = Name & {age: number};

type extends interface

interface Name {
	name: string;
} 

type User = Name & {age: number};

不同点

语法不同
  • 注意,类型别名与字符串字面量类型都是使用 type 进行定义。

interface

interface User {
	name: string
	age: number
}

type

type Name = string

type User = {
	name: Name
}
运用场景不同
  • type 是类型别名,类型别名常用于联合类型,本身并不是一种类型,用于给各种类型定义别名,让TS写起来更简洁、清晰。
  • interface 是接口,用于描述一个对象
使用方式不同
  • type 可以声明基本类型、联合类型、交叉类型、元组
  • interface 可以合并重复声明
type Name = string                              // 基本类型

type arrItem = number | string                  // 联合类型

const arr: arrItem[] = [1,'2', 3]

type Person = { 
  name: Name 
}

type Student = Person & { grade: number  }       // 交叉类型

type Teacher = Person & { major: string  } 

type StudentAndTeacherList = [Student, Teacher]  // 元组类型

const list:StudentAndTeacherList = [
  { name: 'lin', grade: 100 }, 
  { name: 'liu', major: 'Chinese' }
]

interface 可以合并重复声明

interface Person {
    name: string
}

interface Person {         // 重复声明 interface,就合并了
    age: number
}

const person: Person = {
    name: 'lin',
    age: 18
}

type 不能合并重复声明

type Person = {
    name: string
}

type Person = {     // 报错 Duplicate identifier 'Person'
    age: number
}

const person: Person = {
    name: 'lin',
    age: 18
}

结论:

type可以通过给自定义类型命名的方式,适用于任何场景,功能面最广。
interface通常适用于对象的定义。

点这看详情
https://ts.xcatliu.com/advanced/class-and-interfaces.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值