TypeScript
简称:TS,是 JavaScript 的超集,简单来说就是:JS 有的 TS 都有。从编程语言的动静来区分,TypeScript 属于静态类型的编程语言,JavaScript 属于动态类型的编程语言,TS在编译期做类型检查,而JS是在执行期做类型检查,在这一点上来说,TS能更早的发现错误,这样可以减少我们查找、修改bug的时间。
使用TS需要编译器,因为浏览器不认识TS,需要工具将TS转化为JS
安装命令:npm i -g typescript
或者 yarn global add typescript
验证是否安装成功:tsc –v
原始数据类型:number / string / boolean / null / undefined
// number
let a:number = 10
// boolean
let b:boolean = false
// string
let c:string = '1234'
const d:undefined = undefined
console.log(d)
const e:null = null
console.log(e)
其中,undefined
和 null
是所有类型的子类型,也就是说 undefined
/ null 类型的变量,可以赋值给 number
类型的变量,而 void
类型的变量不能赋值给 number
类型的变量
let n:string = null/undefined
空值:在 TypeScript 中,可以用 void
表示没有任何返回值的函数
// void 空值,表示没有任何返回值的函数
function fn1():void {
console.log(123456)
}
console.log(fn1())
任意值:any
// any 任何类型
let h:any = 123
h = null
h = undefined
h = []
h = new String()
h = String
console.log(h)
let newARR:any[]=[,2,3,'',true]
console.log(newARR)
数组:有两种写法
// Array
let arr:number[] = [1, 2, 3]
arr = [4, 5, 6]
console.log(arr)
const arr1:Array<number> = [7, 8, 9]
console.log(arr1)
对象
let obj:object = {}
类型推论:TS会在没有明确的指定类型的时候推测出一个类型
let g;
联合类型:表示取值可以为多种类型中的一种
注意:当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法
接口:它是对行为的抽象,用于对「对象的形状(Shape)」进行描述,简单理解为一种约束
// 定义接口
interface IPerson{
id:number,
name: string,
age: number,
}
let p:IPerson={
id: 123321,
name: 'tom',
age: 19,
}
console.log(p)
可以看到,p与IPerson的变量和变量的类型是一致的,因为我们约束了p的形状必须和接口IPerson一致,定义的变量比接口少了一些、多了一些属性是不允许的,会报错。
接口-可选属性:有时不想完全匹配一个形状,这时候可以使用可选属性,在定义的属性名后面机上?即可,这样在调用接口是不定义sex属性也不会报错。
sex?: string
但是这时候仍然不允许添加未定义的属性,比如接口里只有A、B、C三种属性,定义对象T时,T里面多出了个D属性,会直接报错。
接口-任意属性:有时想要一个任意属性在接口里,可以这么定义
[propName:string]:any
使用 [propName: string] 定义了任意属性取 string 类型的值,需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集,例如定义[propName:string]:string, 确定属性和可选属性只能定义为string的子集。
一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:
[propName: string]: string | number;
接口-只读属性:希望某个属性只能只读,定义时在属性前加 readonly
readonly id:number,
也可以用接口来表示数组
interface INewArray{
[index:number]:number
}
let arr2:INewArray = [1,2,3,4]
INewArray表示:只要索引的类型是数字时,那么值的类型必须是数字。
函数:JS中的函数定义方式
// 函数声明 命名函数
function add(a,b){
return a+b
}
// 函数表达式 匿名函数
let add = function(a,b){
return a+b
}
TS中为:
function add(a:number, b:number):number{
return a+b
}
let f:number=add(1,2)
console.log(f)
// 函数表达式 匿名函数
let add1=function(a:number, b:number):number{
return a+b
}
let g:number=add1(3,4)
console.log(g)
完整写法为:
let add2:(a:number, b:number)=>number=function(a:number, b:number):number{
return a+b
}
let j:number=add2(4,5)
console.log(j)
接口约束函数
// 约束函数就是约束函数的 参数与返回值
interface ISearchFunc{
// (参数:类型,...):返回值的类型
(a:string, b:string):boolean
}
const fun1:ISearchFunc=function(a:string, b:string):boolean{
return a.search(b) !== -1
}
console.log(fun1('123', '2'))
上方约束了参数与返回值的类型
函数的可选参数和默认参数:
// 可选参数 ? 必选参数不能放在可选参数后面
// 默认参数 不传值时使用默认参数定义的值,且可以放在必选参数和可选参数后面
let getName=function(x:string, y?:string, z:string='李'):string {
return x + y + z
}
console.log(getName('张'))
可以看到,此时只传了一个参数进去,这个参数会被X接收,输出结果为
张undefined李
这时再传另一个值进去(假设传了个字符串三),这个值会被可选属性Y接收,输出结果为
张三李
如果传了三个值进去,输出结果则为传的那三个值,而定义好的默认参数Z的值->李 会被顶掉
函数的剩余参数:
function fn(a:string,b:string, ...args:number[]){
console.log(a,b,args);
}
fn('','',1,2,3,4,5)
函数重载:重载允许一个函数接受不同数量或类型的参数时,作出不同的处理
假如定义一个函数,要求当传入数值时,输出结果为数值;传入字符串,输出结果为字符串,这时可以使用重载,提前定义好接收什么时输出什么。假如不使用重载,这时有可能x接收的是数值,y接收的是字符串,显然这不是想要的结果,因为这不能够精确地做出表达。
function newArr(x:string, y:string):string
function newArr(x:number, y:number):number
function newArr(x:string|number, y:string|number ):string|number|void{
if (typeof x == 'string' && typeof y == 'string') {
return x + y
} else if (typeof x == 'number' && typeof y == 'number') {
return x + y
}
}
console.log(newArr(1,2));
console.log(newArr('张','两三百'));
注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
类型断言:手动指定一个值的类型
语法:值 as 类型 或者 <类型>值
例子:定义一个函数,获取传入值(数字、字符串)的长度
// 一、将一个联合类型断言为其中一个类型
function getLength(x:string|number):number|void{
// return x.toString().length
if((x as string).length) {
return (<string>x).length
} else if (typeof x == 'number') {
return x.toString().length
}
}
console.log(getLength('asoidaonbf'));
console.log(getLength(123));
// 二、将任何一个类型断言为 any,any类型可以访问任何属性和任何方法
// ===> 它极有可能掩盖了真正的类型错误,所以如果不是非常确定,就不要使用 as any
(window as any).l = 10
// 三、将 any 断言为一个具体的类型
function abc(x:any, y:any):any{
return x + y
}
let l = abc(1,2) as number
// let l = abc('4','45') as string
断言总结:
- 联合类型可以被断言为其中一个类型
- 任何类型都可以被断言为 any
- any 可以被断言为任何类型
类型别名:给一个类型起个新名字
// 类型别名 常用于联合类型
type snb = string | number | boolean
let abcd:snb = 123
let abcde:snb = '321'
let abcdef:snb = true
abcde=123
字符串字面量:用来约束取值只能是某几个字符串中的一个
type stringType = '123' | '456' | '789'
let names:stringType = '123'
元组:
// 数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。
let arr3:number[]=[1,23,3.4]
let Tarr:[number, string] = [123, '456']
// 添加内容的时候, 只需要是元组里定义的类型即可,顺序不管
Tarr.push(111)
Tarr.push('222')
枚举:用于取值被限定在一定范围内的场景,枚举使用 enum 关键字来定义
// 可以理解为定义了一个类型为枚举的对象
enum NumberType{
one=1, // (one=1) ==> 手动赋值,如果没有赋值的话(one),第一个参数默认为0,而后递增下去
two,
three,
four
}
console.log(NumberType.one);
上面会被编译为这样的JS:
var NumberType;
(function (NumberType) {
NumberType[NumberType["one"] = 1] = "one";
NumberType[NumberType["two"] = 2] = "two";
NumberType[NumberType["three"] = 3] = "three";
NumberType[NumberType["four"] = 4] = "four";
})(NumberType || (NumberType = {}));
console.log(NumberType.one);
从被编译后文件可以得知:
可以通过名称获取到值,也可以通过值获取名称;
如果是按顺序手动赋值(递增),当给one赋值1,后面的可以不赋值,因为会自动递增+1;
后一个值总会根据前一个值的值去递增,比如one给了1,two自动递增+1赋值为2,three赋值为10,four则会递增+1赋值位1;
注意:尽量不要赋同样的值,否则前一个值会被后一个值直接覆盖
枚举项:常数项和计算所得项
例如
enum Color{
red, // 常数项
blue='blue'.length, // 计算所得项
green=11
}
blue需要通过计算’blue’的长度得来,所以blue是计算所得项
注意:计算所得项要放在已经确定赋值的枚举项之前,计算所得项后不要接未赋值的常数项,会报错,除非接上去的是手动赋值
常数枚举:使用 const enum 定义的枚举类型
const enum Obj{
o,
b,
j
}
console.log(Obj.o);
console.log(Obj.b);
console.log(Obj.j);
它的JS编译结果为:
console.log(0 /* Obj.o */);
console.log(1 /* Obj.b */);
console.log(2 /* Obj.j */);
可以看出:常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员
外部枚举: 使用 declare enum 定义的枚举类型
declare enum ABC{
a,b,c
}
console.log(ABC.a);
console.log(ABC.a);
declare 定义的类型只会用于编译时的检查,编译结果中会被删除。
外部枚举同样也会在编译结果中被删除掉,但是输出的是原封不动的,例如在常数枚举中输出的结果是该枚举项的值,而在外部枚举中输出的是该枚举项,而不是枚举项的值
// 可以和const同时使用
declare const enum DEF{
a,b,c
}
console.log(DEF.a);
类:描述了所创建对象的 共同的属性和方法
在JS中实例化一个对象:
class Person{
constructor(name, age){
this.name = name
this.age = age
}
}
new Person() // 在new的时候,会执行类中的构造方法constructor
typescript中:
class Person{
name:string
age:number
constructor(name:string, age:number){
this.name = name
this.age = age
}
SayHi(str:string){
console.log('hi,' + str);
}
}
let pp = new Person('张三', 12)
pp.SayHi('666')
注意:在ts中写属性,需要提前给属性定好是什么类型,才能在constructor中this添加对应的属性和方法
类的继承:通过继承,扩展现有的类
class Animal{
name:string
age:number
constructor(name:string, age:number){
this.name = name
this.age = age
}
SayHi(str:string){
console.log('hi,' + str);
}
}
class Dog extends Animal{
constructor(name:string, age:number){
// 使用super,调用父类的构造函数constructor
super(name, age)
}
// 可以调用父类的方法
// SayHi(str:string){
// console.log('hi,' + str);
// }
// 也可以重写方法
SayHi(){
console.log('hi,我是dog类的边牧');
// 也可以在这里调用父类的方法
super.SayHi('一号')
}
}
let dog = new Dog('边牧', 6)
dog.SayHi()
输出结果:
hi,我是dog类的边牧
hi,一号
类的存储器:控制对对象成员的访问
class Name{
firstName:string
lastName:string
constructor(firstName:string, lastName:string){
this.firstName = firstName
this.lastName = lastName
}
// 读取器
get fullName(){
return this.firstName + '-' + this.lastName
// return '张三丰'
}
// 设置器
set fullName(value) {
console.log(value);
let names=value.split('-')
this.firstName = names[0]
this.lastName = names[1]
}
}
const ppp = new Name('张', '二')
console.log(ppp);
ppp.fullName='李-大头'
console.log(ppp.fullName);
输出结果:
Name { firstName: '张', lastName: '二' }
李-大头
李-大头
类的静态方法:使用 static
修饰符修饰
// 静态属性 ===> 只属于类自己的属性和方法 --> 只能自己调用
class aa{
static name1: string;
// constructor(name:string){
// this.name = name
// }
static sayHi(){
console.log('777');
}
}
const aa1 = new aa()
console.log(aa.name1);
aa.sayHi()
// console.log(aa1.name1); 报错
// aa1.sayHi() 报错
类的修饰符:TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected。
public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的。
private 修饰的属性或方法是私有的,不能在声明它的类的外部访问。
protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的。
class B{
private name1:string
protected age:number
public constructor(name1:string, age:number){
this.name1=name1
this.age=age
}
public p(){
console.log('444');
console.log(this.name1); // private name1 只能在类B里访问
console.log(this.age);
}
}
class C extends B{
constructor(name1:string,age:number){
super(name1,age)
}
play(){
// console.log(this.name1);报错
console.log(this.age);
}
}
const bb = new B('张三', 12)
// console.log(bb.name1); 报错->只能在类B里访问 ↑
bb.p()
const cc= new C('李四', 13)
cc.play() // 父类共有的方法继承下来,子类可以使用
// console.log(cc.name1); 报错->只能在类B里访问 继承下来了但无法使用
父类使用了private定义的属性或方法,可以被子类继承,但子类内部无法访问,被private的属性只能被父类使用
readonly修饰符:
class X{
readonly age:number // 只读属性,但是可以在构造函数里修改
// 如果readonly以及三个修饰符定义在参数上,那就是创建并初始化这个参数
constructor(age:number){
this.age=age
}
update(){
// this.age=12 报错
}
}
类的类型:把类作为类型使用
class Car {
name:string
constructor(name:string) {
this.name = name
}
}
class Ben extends Car {
constructor(name:string) {
super(name)
}
}
const ben : Car = new Ben('')
注意:子类 与 父类 需要有同样的属性 如果子类出现父类没有的属性 则父类不能被当成类型使用
类实现接口:使用 implements
关键字来实现
interface ISing {
sing():any
}
interface IDance {
dance():any
}
class pppp implements ISing, IDance{
sing(){
console.log('唱歌');
}
dance() {
console.log('跳舞');
}
}
class an implements ISing, IDance{
sing(){
console.log('唱歌');
}
dance() {
console.log('跳舞');
}
}
const p1 = new pppp()
p1.sing()
p1.dance()
const p2 = new an()
p2.sing()
p2.dance()
接口继承接口:
// 接口可以继承其他多个接口
interface IRun {
run():any
}
interface ISwim {
swim():any
}
interface IActive extends IRun,ISwim{
}
class I implements IActive {
run() {
console.log("I在run");
}
swim () {
console.log('I在swim');
}
}
const ii = new I()
ii.run()
ii.swim()
接口继承类:
class newPerson {
name:string
constructor(name:string){
this.name = name
}
sayHi(){
console.log(123);
}
}
interface IPeerson extends newPerson { // 接口继承类中的实例属性和实例方法
age:number
}
let person:IPeerson = {
name: '',
age: 12,
sayHi() {
console.log(456);
}
}
person.sayHi()
接口合并:
// 相同属性的合并,类型需一致
interface cat{
name: string
age:number
}
interface cat {
name:string
gender:string
}
const catt:cat = {
name: '橘猫',
age: 12,
gender: '女'
}
泛型:指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
不使用泛型:
// 需求:定义一个函数,传入两个参数,第一个参数是数据,第二个参数是数量
// 函数的作用:根据数量产生对应数量的数据,存放在数组中
function getArr(value:number, count:number):number[]{
const arr = []
for(let i = 0; i < count; i++){
arr.push(value)
}
return arr
}
console.log(getArr(123, 4));
使用泛型:
// 使用泛型,定义时不需要先确定类型,使用的时候才来确定
// T 表示任意输入的类型
function getArr<T>(value:T, count:number):T[]{
const arr:T[] = []
for(let i = 0; i < count; i++){
arr.push(value)
}
return arr
}
console.log(getArr(123, 4)); // 不写类型 -> <number>时 会自己做类型推断
console.log(getArr<string>('321', 5));
输出结果:
[ 123, 123, 123, 123 ]
[ '321', '321', '321', '321', '321' ]
多泛型参数的函数:
function updateArr<T, U>(t: [T, U]): [U, T] {
return [t[1], t[0]]
}
console.log(updateArr<string, number>(['123', 123]));
console.log(updateArr<boolean, number>([true, 123]));
泛型约束:约束泛型必须包含某种东西。
例如,约束输入的类型,必须要有length属性:
interface ILength{
length:number
}
function getArrLength<T extends ILength>(x:T):number{
return x.length
}
console.log(getArrLength('123'));
假如这时传入的值不包含length属性,例如数值123,就会报错。
泛型接口:使用含有泛型的接口来定义函数的形状
interface IArr {
<T>(value: T, count: number):Array<T>
}
let getArr1:IArr = function <T>(value:T, count:number):T[]{
const arr:T[] = []
for(let i = 0; i < count; i++){
arr.push(value)
}
return arr
}
console.log(getArr1('333', 7));
泛型类:
class Pperson<T>{
name:string
age: T
constructor(name:string, age:T){
this.name = name
this.age = age
}
}
const pperson = new Pperson<number>('123', 456)
const pperson1 = new Pperson<string>('123', '456')