TS
Typed JavaScript at Any Scale. 它强调了 TypeScript 的两个最重要的特性——类型系统、适用于任何规模。
我们知道,JavaScript 是一门非常灵活的编程语言,
- 它没有类型约束,一个变量可能初始化时是字符串,过一会儿又被赋值为数字。
- 由于隐式类型转换的存在,有的变量的类型很难在运行前就确定。
- 基于原型的面向对象编程,使得原型上的属性或方法可以在运行时被修改。
- 函数是 JavaScript中的一等公民,可以赋值给变量,也可以当作参数或返回值。
- JavaScript 本身是一种动态类型语言,这意味着变量可以改变类型。从 TypeScript 的名字就可以看出来,「类型」是其最核心的特性。使用 TypeScript 的主要原因是就是为了给 JavaScript 添加静态类型。静态类型意味着变量的类型在程序中的任何时候都不能改变。它可以防止很多bug !
TS是静态类型
分为两类分别是静态类型和动态类型。
动态类型是指在运行时才会进行类型检查,这种语言的类型错误往往会导致运行时错误。JavaScript 是一门解释型语言,没有编译阶段,所以它是动态类型
静态类型是指编译阶段就能确定每个变量的类型,这种语言的类型错误往往会导致语法错误。TypeScript 在运行前需要先编译为 JavaScript,而在编译阶段就会进行类型检查,所以 TypeScript 是静态类型
TypeScript 的特点
- TypeScript 可以编译出纯净、 简洁的 JavaScript 代码,并且可以运行在任何浏览器上、Node.js 环境中和任何支持 ECMAScript 3(或更高版本)的JavaScript 引擎中。
- 类型系统允许 JavaScript 开发者在开发 JavaScript 应用程序时使用高效的开发工具和常用操作比如静态检查和代码重构。
- TypeScript 提供最新的和不断发展的 JavaScript 特性,包括那些来自 2015 年的 ECMAScript 和未来的提案中的特性,比如异步功能和 Decorators,以帮助建立健壮的组件。
js和ts的区别
Typescript | JavaScript |
---|---|
JavaScript 的超集,用于解决大型项目的代码复杂性 | 一种脚本语言,用于创建动态网页。 |
强类型,支持静态和动态类型 | 动态弱类型语言 |
可以在编译期间发现并纠正错误 | 只能在运行时发现错误 |
不允许改变变量的数据类型 | 变量可以被赋予不同类型的值 |
javaScript 属于动态编程语言,而ts 属于静态编程语言。
。js: 边解释边执行,错误只有在运行的时候才能发现
先编译再执行,在写的时候就会发现错误了 (ts不能直接执行,需要先编译成 is )
ts: ts 完全支持 is,可以直接转换
TS 的缺点:
不能被浏览器理解,需要被编译成 JS
有学习成本,写习惯了JS 的我们要上手需要花时间去理解,而且 TS 中有一些概念还是有点难,比如泛型,
安装 TypeScript
npm install -g typescript1
安装完成后,在控制台运行如下命令,检查安装是否成功
tsc -V
TypeScript常用语法
基础类型
TypeScript 支持与 JavaScript 几乎相同的数据类型,此外还提供了实用的枚举类型方便我们使用。 BigInt
1. 布尔值
最基本的数据类型就是简单的 true/false 值,在JavaScript 和 TypeScript 里叫做 boolean
(其它语言中也一样)。
// 布尔类型的
// 基本语法
// let 变量名:数据类型 = 值
let flag:boolean=true;
flag = false;
console.log(flag);
2. 数字
和 JavaScript 一样,TypeScript 里的所有数字都是浮点数。 这些浮点数的类型是 number。 除了支持十进制和十六进制字面量,TypeScript 还支持 ECMAScript 2015中引入的二进制和八进制字面量。
// 数字类型的 number
let a1 :number = 10;//十进制
let a2 :number = 0b1010; //二进制
let a3 :number = 0o12; //八进制
let a4 :number = 0xa; //十六进制
console.log(a1);
console.log(a2);
console.log(a3);
console.log(a4);
3. 字符串
JavaScript 程序的另一项基本操作是处理网页或服务器端的文本数据。 像其它语言里一样,我们使用 string
表示文本数据类型。 和 JavaScript 一样,可以使用双引号("
)或单引号('
)表示字符串。
// 字符串类型的 string
let str1:string = "床前明月光"
let str2:string = "小明去开窗"
let str3:string = "遇到一耳光"
let str4:string = "牙齿掉光光"
console.log(`${str1},${str2},${str3},${str4}`);
// 字符串和数字类型进行拼接
let str5 :string = "我有这么多钱"
let num :number = 100000;
console.log(str5+num);
// 总结 ts中变量一开始是什么类型的,只能用这个类型的数据是不允许用其他类型的数据赋值给当前的这个变量中
4. undefined 和 null
TypeScript 里,undefined
和 null
两者各自有自己的类型分别叫做 undefined
和 null
。 它们的本身的类型用处不是很大:
let und:undefined = undefined;
let nul:null = null;
console.log(und);
console.log(nul);
// undefined和null都可以作为其他类型的,把undefined和null赋值给其他数据类型的变量的,如number类型的变量
let num2 :number = undefined;
console.log(num2);
默认情况下 null
和 undefined
是所有类型的子类型。 就是说你可以把 null
和 undefined
赋值给 number
类型的变量。
5. object
object
表示非原始类型,也就是除 number
,string
,boolean
之外的类型。
使用 object
类型,就可以更好的表示像 Object.create
这样的 API
。例如:
// object类型
// 定义一个函数,参数是object类型,返回值也是object类型
function getObj(obj:object):object{
console.log(obj);
return{
name:"徐艺豪",
age:18
}
}
console.log(getObj({name:"安龙",age:10}));
// console.log(getObj('1234')); //错误的
console.log(getObj (new String ("455454554")));
6. never
never 类型是任何类型的子类型,也可以赋值给任何类型。
没有类型是 never 的子类型,没有类型可以赋值给 never 类型(除了 never 本身之外)。即使 any
也不可以赋值给 never 。
7. any
不清楚用什么类型,可以使用 any 类型。
// any类型
let str7:any = 100;
str7 = "徐艺豪"
console.log(str7);
// 当一个数组中要存储多个数据,个数不确定,此时也可以使用any类型来定义数组
let arr7:any[] = [10,"徐艺豪",true]
console.log(arr7);
// 这种情况下也没有错误的提示信息,any类型有优点,也有缺点 为什么呢因为第一个是数字类型,数字类型是没有split方法的
console.log(arr7[1].split(""));
8. unknown
不建议使用 any,当我不知道一个类型具体是什么时,该怎么办?
可以使用 unknown 类型
unknown 类型代表任何类型,它的定义和 any 定义很像,但是它是一个安全类型,使用 unknown 做任何事情都是不合法的。
比如,这样一个 divide 函数,
function divide(param: any) {
return param / 2;
}
把 param 定义为 any 类型,TS 就能编译通过,没有把潜在的风险暴露出来,万一传的不是 number 类型,不就没有达到预期了吗。
把 param 定义为 unknown 类型 ,TS 编译器就能拦住潜在风险,如下代码显示,
function divide(param: unknown) {
return param / 2;
}
因为不知道 param 的类型,使用运算符 /,导致报错。
再配合类型断言,即可解决这个问题,
function divide(param: unknown) {
return param as number / 2;
}
9. void
某种程度上来说,void
类型像是与 any
类型相反,它表示没有任何类型
。 当一个函数没有返回值时,你通常会见到其返回值类型是 void
:
// void类型 在函数声明的时候,小括号的后面使用:void,代表的是该函数没有任何的返回值
function showMsg():void{
console.log("徐艺豪");
}
console.log(showMsg()); //输出结果为undefined
let vd:void = true //可以编译出来,但是会报错
console.log(vd);
10. 数组
TypeScript 像 JavaScript 一样可以操作数组元素。 有两种方式可以定义数组。 第一种,可以在元素类型后面接上[]
,表示由此类型元素组成的一个数组:
// 数组类型
// 数组定义方式1
// let变量名:数据类型[] = [值1,值2,值3]
let arr1:number[] = [10,20,30,40,60,70]
console.log(arr1);
// 数组定义方式2:泛型的写法
let arr2:Array<number> = [100,2003,300]
console.log(arr2);
// 注意问题:数组定义后,里面的数据类型必须和定义数组的时候类型是一致的,否则会报错的
11. 元组 Tuple
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同
。 比如,你可以定义一对值分别为 string
和 number
类型的元组。
// 元组类型:在定义数组的时候,类型和数据的个数一开始就已经限定了
let arr3 :[string,number,boolean] = ["徐艺豪",12,true]
console.log(arr3);
// 注意问题:元组类型在使用的时候,数据类型的位置和数据的个数,应该和在定义元组的时候的数据类型及位置应该是一致的
console.log(arr3[0].split(''));
console.log(arr3[1].toFixed(2));
12. 枚举
enum
类型是对 JavaScript 标准数据类型的一个补充。 使用枚举类型可以为一组数值赋予友好的名字
。
// 枚举
// enum类型是对javascript标准数据类型的一个补充,使用枚举可以为 一组数值赋予友好的名字
console.log("=========================");
// 枚举类型,枚举里面的每个数据值都可以叫元素,每个元素都有自己的编号,编号是从0开始的,依次递增加1
enum Color{
red,
green,
blue,
}
// 默认情况下,从0开始为元素编号,你也可以手动的指定成员的数值,我们将上面的例子改成从1开始编号
// 定义一个Color的枚举类型的变量来接收枚举的值
console.log(Color.red,Color.green,Color.blue);
console.log(Color[2]);
// 小例子
enum Green{
女,
男,
}
console.log(Green[1]);
// 可以中文,但是不建议使用中文
类型
1. 联合类型
联合类型(Union Types)表示取值可以为多种类型中的一种
需求1: 定义一个一个函数得到一个数字或字符串值的字符串形式值
// 联合类型
// 联合类型Union Types表示取值可以为多种类型中的一种
// 需求1定义一个函数得到一个数字或字符串值的字符串形式值
function getStr(str:number|string):string{
return str.toString();
// return Number(str)
}
console.log(getStr("123"));
需求2: 定义一个一个函数得到一个数字或字符串值的长度
2. 类型断言
通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript 会假设你,程序员,已经进行了必须的检查。
类型断言有两种形式。 其一是“尖括号”语法, 另一个为 as 语法
// 类型断言:告诉编译器,我知道自己是什么类型的,也知道自己在干什么
// 类型断言的语法方式1:<类型>变量名
// 类型断言的语法方式2:值as类型
if((<string>str).length){
// str.length存在吗?如果存在也就说明str是string类型的
return (str as string).length
}else{
return str.toString().length
}
}
console.log(getStr("123"));
console.log(getStr(123455666));
3. 类型推断
类型推断: TS会在没有明确的指定类型的时候推测出一个类型
有下面2种情况: 1. 定义变量时赋值了, 推断为对应的类型. 2. 定义变量时没有赋值, 推断为any类型
// 类型推断:在没有明确指定类型的时候推测出一个类型
// 根据第一个初始化的值,来进行判断看它是什么类型的
// let txt = 100
// txt = "飞哥牛逼";
// console.log(txt);
let txt2; //any类型 也就是它可以是任意数据类型的
txt2 = 100;
txt2 = "你真厉害啊",
console.log(txt2);
4.交叉类型
如果要对对象形状进行扩展,可以使用交叉类型 &
。
比如 Person 有 name 和 age 的属性,而 Student 在 name 和 age 的基础上还有 grade 属性,就可以这么写,
let k: { name: string } & { age: number };
k = { name: '张瑾年', age: 18 };
接口 interface
.1.基本概念
接口: 接口是TS设计出来用于定义对象类型的,可以对对象的形状进行描述。
定义interface一般首字母大写。
interface Person {
name: string
age: number
}
const p1: Person = {
name: 'lin',
age: 18
}
2.可选属性
interface Person {
name: string
age?: number
}
const p1: Person = {
name: 'lin',
}
3.只读属性
interface Person {
readonly id: number
name: string
age: number
}
改变这个只读属性时会报错。
类
1. 基本写法
class Person {
name: string
constructor(name: string) {
this.name = name
}
speak() {
console.log(`${this.name} "牛逼"`)
}
}
const p1 = new Person('lin') // 新建实例
p1.name // 访问属性和方法
p1.speak()
2. 类实现接口
- 与 C# 或 Java 里接口的基本作用一样,TypeScript 也能够用它来明确的强制一个类去符合某种契约。
// 类 类型:指的是类的类型可以通过接口来实现
// 类实现接口
// 定义一个接口
interface Ifly{
//
fly()
}
// 定义一个类,这个类就是上面定义的接口(实际上也可以理解为,Ifly接口来约束了当前的这个person类)
class Person implements Ifly{//这个类实现了这个接口
// 实现接口中的方法
fly(){
console.log("我是你");
}
}
// 实例化对象
const person =new Person();
person.fly()
3. 一个类可以实现多个接口
class Car2 implements Alarm, Light {
alert() {
console.log('nice');
}
lightOn() {
console.log('徐艺豪');
}
lightOff() {
console.log('安龙');
}
}
//接口继承接口
interface LightableAlarm extends Alarm, Light {
}
4. 继承
类与类之间的关系
继承后类与类之间的叫法
A类继承了B这个类,那么此时A类叫子类,而B类叫基类
子类---->也叫派生类
基类—>超类(父类)
一旦发生了继承的关系,就出现了父子类的关系(叫法)
在 TypeScript 里,我们可以使用常用的面向对象模式。 基于类的程序设计中一种最基本的模式是允许使用继承来扩展现有的类。
// 类与类之间的关系
// 继承后类与类之间的叫法
// A类继承了B这个类,那么此时A类叫子类,而B类叫基类
// 子类---->也叫派生类
// 基类--->超类(父类)
// 一旦发生了继承的关系,就出现了父子类的关系(叫法)
// class Person{
// // 来定义一个类
// name:string //名字
// age:number //年龄
// gender:string //性别
// // 定义构造函数
// constructor(name:string,age:number,gender:string){
// this.name = name
// this.age = age
// this.gender = gender
// }
// // 定义实例方法
// saHi(str:string){
// console.log(`我是:${this.name},${str},${this.age},${this.gender}`);
// }
// }
// // 定义一个类 继承Person
// class Student extends Person{
// constructor(name:string,age:number,gender:string){
// // 调用父类中的构造函数,使用super关键字
// super(name,age,gender)
// }
// saHi(str:string){
// super.saHi(`哈哈,${str}`)
// }
// }
// const person = new Person("大名",18,"你好")
// person.saHi("徐艺豪")
// const stu = new Student("小哥哥",18,"男")
// stu.saHi("崔彦鹏")
// 总结:类和类之间如果要有继承的关系,需要使用extends关键字
// 子类中可以调用父类中的构造函数,使用的是super关键字(包括调用父类中的实例方法,可以使用super)
// 子类可以重写父类中的方法
5. 多态
多态:父类型的引用指向了子类的对象,不同类型的对象针对相同的方法,产生了不同的行为。
// 定义一个父类
class Animal{
// 定义一个属性
name:string
// 定义一个构造函数
constructor(name:string ){
// 更新属性值
this.name = name
}
// 实例方法
run(distance:number = 0){
console.log(`跑了${distance}米这么远来通宵`);
}
}
// 定义一个子类
class Dog extends Animal{
// 构造函数
constructor(name:string){
// 调用子类的构造函数,实现了类中属性的初始化操作
super(name)
}
// 实例方法,重写父类中的实例方法
run(distance:number= 5){
console.log(`跑了${distance}米这么远的距离${this.name}`);
}
}
// 定义一个子类
class Pig extends Animal{
// 构造函数
constructor(name:string){
// 调用子类的构造函数,实现了类中属性的初始化操作
super(name)
}
// 实例方法,重写父类中的实例方法
run(distance:number= 100){
console.log(`跑了${distance}米这么远的距离${this.name}`);
}
}
// 实例化父类对象
const ani :Animal = new Animal("动物")
ani.run()
// 实例化子类对象
const dog:Dog = new Dog("大黄")
dog.run()
// 实例化子类对象
const pig:Pig = new Pig("荷兰猪")
pig.run()
console.log("================================");
// 父类和子类的关系:父子关系,此时,父类类型创建了子类对象
const dog1:Animal = new Dog("小黄")
dog1.run()
const pig1:Animal = new Pig("小猪")
pig1.run()
console.log("----------------------------");
// 该函数需要的参数是Animal类型的
function shoe(ani:Animal){
ani.run()
}
shoe(dog1)
shoe(pig1)
6. 公共,私有与受保护的修饰符
默认为 public
在下面的例子里,我们可以自由的访问程序里定义的成员。 如果你对其它语言中的类比较了解,就会注意到我们在之前的代码里并没有使用 public 来做修饰;例如,C# 要求必须明确地使用 public 指定成员是可见的。 在 TypeScript 里,成员都默认为 public。
你也可以明确的将一个成员标记成 public。 我们可以用下面的方式来重写下面的 Animal 类:
理解 private
当成员被标记成 private 时,它就不能在声明它的类的外部访问。
理解 protected
protected 修饰符与 private 修饰符的行为很相似,但有一点不同,protected成员在派生类中仍然可以访问。
// 修饰符(类中的成员的修饰符):主要是描述类中的成员(属性,构造函数,方法)的可访问性
// 类中的成员都有自己的默认访问修饰符,public
// public修饰符 ---->公共的,是类中成员的修饰符,代表的是公共的,任何位置都可以访问类中的成员
// private修饰符 ---->私有的,类中的成员如果使用了private来修饰,那么外部是无法访问的,当然子类中也是无法访问该成员数据的
// protected ---->受保护的修饰了属性成员 子类成员可以访问,外部成员是无法访问的,
// import { readonly } from "vue";
// // 定义一个类
// class Person {
// // 属性 public修饰了属性成员
// // public name: string
// // private修饰了属性成员 私有的只有Person能访问
// // private name: string
// // protected修饰了属性成员 子类成员可以访问,
// protected name: string
// // 构造函数
// public constructor(name: string) {
// this.name = name
// }
// // 方法
// public eat() {
// console.log(`你真棒啊`, this.name);
// }
// }
// // 定义一个子类
// class Student extends Person {
// // 构造函数
// constructor(name: string) {
// super(name)
// }
// play() {
// console.log("我就喜欢有用", this.name);
// }
// }
// // 实例化对象
// const per = new Person("徐艺豪")
// // 类的外部可以访问类中的属性成员
// // console.log(per.name);
// const stu = new Student("红豆");
// stu.play()
// // console.log(stu.name);
// per.eat()
7. 存取器
TypeScript
支持通过 getters/setters
来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。
下面来看如何把一个简单的类改写成使用 get
和 set
。 首先,我们从一个没有使用存取器的例子开始。
// 存取器:让我们可以有效的控制对象中的成员访问,通过getters和setters来进行操作
// 外部可以传入姓氏和名字数据,同时使用set和get控制姓名的数据,外部也可以进行修改操作
class Person {
firstName:string //姓氏
lastName:string//名字
constructor(firstName:string,lastName:string){
this.firstName = firstName
this.lastName = lastName
}
// 姓名的成员属性(外部可以访问,也可以修改)
// 读取器---负责读取数据的
get fullName(){
// 姓名===>姓氏和名字的拼接
return this.firstName + "_" + this.lastName
}
// 设置器---负责设置数据的(修改)
set fullName(val){
// 姓名----->把姓氏和名字获取到重新的赋值给firstName和lastName
let names = val.split("_")
this.firstName = names[0]
this.lastName= names[1]
}
}
const person:Person = new Person("诸葛","孔明")
console.log(person);
// 获取该属性的成员属性
console.log(person.fullName);
// 设置该属性的数据
person.fullName = "独孤_求败"
// console.log(person.fullName);
8. 静态属性
到目前为止,我们只讨论了类的实例成员,那些仅当类被实例化的时候才会被初始化的属性。 我们也可以创建类的静态成员,这些属性存在于类本身上面而不是类的实例上。 在这个例子里,我们使用 static 定义 origin,因为它是所有网格都会用到的属性。 每个实例想要访问这个属性的时候,都要在 origin 前面加上类名。 如同在实例属性上使用 this.xxx 来访问属性一样,这里我们使用 Grid.xxx 来访问静态属性。
class Person{
public name:string;
static sex = '男' //静态属性
constructor(name:string){
this.name=name;
}
run(){ //实例方法
console.log(`${this.name}在跑步`)
}
work(){
console.log(`${this.name}在上班`)
}
static print(){ //静态方法
console.log('静态方法') //无法直接调用类中属性,例如this.name不可调用,但是可以调用静态属性Person.sex
}
}
var d = new Person('小豪')
d.run() //实例方法调用
Person.print() //静态方法调用 使用静态属性、静态方法前要加类名
9. 抽象类
抽象类做为其它派生类的基类使用。 它们不能被实例化。不同于接口,抽象类可以包含成员的实现细节。 abstract 关键字是用于定义抽象类和在抽象类内部定义抽象方法。
// 抽象类:包含抽象方法(抽象方法一般没有任何具体内容的实现),也可以包含实例方法,抽象类是不能被实例化的,为了让
// 子类进行实例化及实现内部的抽象方法
// 抽象类的目的或者说是作用最终都是为子类服务的
// 定义一个类
abstract class Animal{
// 抽象属性
abstract name :string
// // 抽象方法 不能写具体的实现 报错的
// abstract eat(){
// console.log("跳着吃");
// }
abstract eat()
// 实例方法
saHi(){
console.log("您好啊");
}}
// 定义一个子类(派生类)dog
class Dog extends Animal{
name:string = "徐艺豪"
// 重新的实现抽象类中的方法,此时这个方法就是当前Dog类的实例方法了
eat(){
console.log("好吃,爱吃");
}
}
// 实例化dog对象
const dog:Dog = new Dog()
dog.eat()
//调用的是抽象类的实例方法
dog.saHi()
console.log(dog.name);
10. this类型
class StudyStep {
step1() {
console.log('listen')
return this
}
step2() {
console.log('write')
return this
}
}
const s = new StudyStep()
s.step1().step2() // 链式调用
函数类型
函数是 JavaScript 应用程序的基础,它帮助你实现抽象层,模拟类,信息隐藏和模块。在 TypeScript 里,虽然已经支持类,命名空间和模块,但函数仍然是主要的定义行为的地方。TypeScript 为 JavaScript 函数添加了额外的功能,让我们可以更容易地使用。
和 JavaScript 一样,TypeScript 函数可以创建有名字的函数和匿名函数。你可以随意选择适合应用程序的方式,不论是定义一系列 API 函数还是只使用一次的函数。
// js中的写入方式------>在ts中同样也可以这么写
// function add (x,y){
// return x+ y //求和的函数
// }
// // 函数表达式,匿名函数
// const add2 = function (x,y){
// return x + y
// }
给函数定义类型
function add (x:String,y:string):String{
return x+ y //求和的函数
}
const result1 :string = add ("111","222")
console.log(result1);
// 函数表达式,匿名函数
// 在函数中x和y都是number类型的,小括号后面的:number,代表的是该函数的返回值也是number类型的
const add2 = function (x:number,y:number):number{
return x + y
}
console.log(add2(10,20));
完整函数类型
// 函数的完整写法
// add3---->变量名--->函数add3
// (x:number,y:number)=>number当前的这个函数类型
// function(x:number,y:number):number{return x+y} 就相当于符合上面的这个函数类型的值
let add3:(x:number,y:number)=>number = function(x:number,y:number):number{
return x+y
}
console.log(add3(100,200));
可选参数和默认参数
可选参数和默认参数
可选参数:函数在声明的时候,内部使用了?进行修饰,那么就表示该参数可以传入,也可以不用传入
默认参数:在函数声明的时候,内部的参数就有自己的默认值,此时这个参数就可以叫默认参数
// 可选参数和默认参数
// 可选参数:函数在声明的时候,内部使用了?进行修饰,那么就表示该参数可以传入,也可以不用传入
// 默认参数:在函数声明的时候,内部的参数就有自己的默认值,此时这个参数就可以叫默认参数
// 定义一个函数:传入姓氏和名字,可以得到姓名(姓氏加名字= 姓名)
// 需求:如果不传入任何内容,娜美就返回默认的姓氏
// 需求:如果之传入姓氏,那么就返回姓氏
// 需求:如果传入姓氏和名字,那么返回来的就是姓名
let get = function(firstName:String = "独孤",lastName?:string):string{
if(lastName){
return firstName + "_" + lastName
}else{
return firstName
}
}
// 函数调用
console.log(get());
// 之传入姓氏
console.log(get("诸葛"));
// 传入姓氏和名字
console.log("独孤","求变");
剩余参数
必要参数,默认参数和可选参数有个共同点:它们表示某一个参数。 有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来。 在 JavaScript 里,你可以使用 arguments
来访问所有传入的参数。
在 TypeScript 里,你可以把所有参数收集到一个变量里:
剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 编译器创建参数数组,名字是你在省略号( …)后面给定的名字,你可以在函数体内使用这个数组。
// 剩余参数(rest参数)
// 剩余参数是放在函数声明的时候所有的参数的最后
function msg (str:String,str2:String,...args:String[]){
console.log(str);//a
console.log(str2); //b
console.log(args);//a,b,c,d,e
}
msg("a","b","c","d","e")
泛型
泛型是指附属于函数、类、接口、类型别名之上的类型,当某个函数的参数,返回值和内部使用时的类型无法确定的情况下,就可以使用泛型来进行约束
下面创建一个函数, 实现功能: 根据指定的数量 count 和数据 value , 创建一个包含 count 个 value 的数组
不用泛型的话,这个函数可能是下面这样:
// // 需求:可以传入任意类型的数据,返回来的是存储这个任意类型数据的数组
// function getArr3 (value:any,count:number):any[]{
// // 根据数据和数量产生一个数组
// let arr: number[] = []
// for(let i = 0; i <count; i++){
// arr.push(value)
// }
// return arr
// }
// let arr1 = getArr3(100.123,3)
// let arr2 = getArr3("徐艺豪",3)
// console.log(arr1);
// console.log(arr2);
// // arr1中存储的是数字类型的数据
// // arr2中存储的是字符串类型的数据
// console.log(arr1[0].toFixed(2)); //没有任何的只能提示的信息,要么有方法名字的提示信息,要么有错误的提示信息
// console.log(arr2[0].split("")); //没有任何的只能提示的信息,要么有方法名字的提示信息,要么有错误的提示信息
使用泛型函数
function getArr4<t> (value:t,count:number):t[]{
// 根据数据和数量产生一个数组
let arr: t[] = []
// const arr:Array<t>
for(let i = 0; i <count; i++){
arr.push(value)
}
return arr
}
let arr1 = getArr4<number>(200.1234,5)
const arr2 = getArr4<string>("123",3)
console.log(arr1);
console.log(arr2);
console.log(arr1[0].toFixed);
console.log(arr2[0].split);
多个泛型参数的函数
一个函数可以定义多个泛型参数
// 多个泛型参数的函数:函数中有多个泛型的参数
function getMsg <t,k>(value:t,value2:k) :[t,k] {
return[value,value2]
}
let arr1 = getMsg<string,number>("jacj",100.322332)
console.log(arr1[0].split);
console.log(arr1[1].toFixed(2));
泛型约束
在 TypeScript 中,我们可以对泛型进行约束,使得泛型只能接受某些类型或者符合某些条件的类型。这样可以增强泛型的灵活性和安全性。下边所示最上边的fun中是没有length这个属性的,我们使用了接口让fun1去继承这个接口,那么此时fun1便有了length这个属性。
// 泛型约束
// function fun<t >(x:t):number{
// return x.length
// };
// fun<string>("hello,itlove")
// // 泛型约束 extends
// interface lengthNum {
// length:number;
// }
// function fun1 <t extends lengthNum>(x:t):number{
// return x.length
// };
// fun1<string>("hellow word")
高级类型
1. 索引类型
从对象中抽取一些属性的值,然后拼接成数组,可以这么写,
const userInfo = {
name: 'lin',
age: '18',
}
function getValues(userInfo: any, keys: string[]) {
return keys.map(key => userInfo[key])
}
// 抽取指定属性的值
console.log(getValues(userInfo, ['name','age'])) // ['lin', '18']
// 抽取obj中没有的属性:
console.log(getValues(userInfo, ['sex','outlook'])) // [undefined, undefined]
虽然 obj 中并不包含 sex 和 outlook 属性,但 TS 编译器并未报错
此时使用 TS 索引类型,对这种情况做类型约束,实现动态属性的检查。
理解索引类型,需先理解 keyof(索引查询)、T[K](索引访问) 和 extends (泛型约束)。
2. keyof(索引查询)
keyof
操作符可以用于获取某种类型的所有键,其返回类型是联合类型。
interface IPerson {
name: string;
age: number;
}
type Test = keyof IPerson; // 'name' | 'age'
3. T[K](索引访问)
T[K]
,表示接口 T 的属性 K 所代表的类型下边name就相当于T而string就相当于k
interface IPerson {
name: string;
age: number;
}
let type1: IPerson['name'] // string
let type2: IPerson['age'] // number
4. 映射类型
TS允许将一个类型映射成另外一个类型。会使用到in关键字 in
操作符,是用来对联合类型实现遍历的。
type Person = "name" | "school" | "major"
type Obj = {
[p in Person]: string
}
ts声明文件
1.declare
当使用第三方库时,很多三方库不是用 TS 写的,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
比如,在 TS 中直接使用 Vue,就会报错,
const app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
这时,我们可以使用 declare
关键字来定义 Vue 的类型,简单写一个模拟一下,
interface VueOption {
el: string,
data: any
}
declare class Vue {
options: VueOption
constructor(options: VueOption)
}
const app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
这样就不会报错了,使用 declare 关键字,相当于告诉 TS 编译器,这个变量(Vue)的类型已经在其他地方定义了,你直接拿去用,别报错。
需要注意的是,declare class Vue
并没有真的定义一个类,只是定义了类 Vue
的类型,仅仅会用于编译时的检查,在编译结果中会被删除。它编译结果是:
2.d.ts
通常我们会把声明语句放到一个单独的文件(Vue.d.ts
)中,这就是声明文件,以 .d.ts
为后缀。
// src/Vue.d.ts
interface VueOption {
el: string,
data: any
}
declare class Vue {
options: VueOption
constructor(options: VueOption)
}
// src/index.ts
const app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
一般来说,ts 会解析项目中所有的 *.ts
文件,当然也包含以 .d.ts
结尾的文件。所以当我们将 Vue.d.ts
放到项目中时,其他所有 *.ts
文件就都可以获得 Vue
的类型定义了。
/path/to/project
├── src
| ├── index.ts
| └── Vue.d.ts
└── tsconfig.json