TypeScript起步入门
typeScript的存在,无疑就是为了更加规范化,是继ES5,ES6之后更加高级的一种javascript的超集。他无法被浏览器所识别,所以就要经过编译过后,编译成普通的es5语法。先从头来了解这玩意怎么用:
1、当然是先下载 npm install -g typescript(不知道npm是什么的先去看node.js),下载完可以在命令行打tsc -v来看版本
2、怎么编译呢? 我们可以用命令tsc index.ts这就能编译出es5语法的js,如果我们每写一点就要编译岂不是很麻烦,看下面:
首先在我们项目文件夹输入 tsc --init ,之后会有一个config文件
将outDir解开,后边是输出js的相对路径
。
TypeScript数据类型
首先我们要知道常用的数据类型有哪些,平时我们在js中常用的也就bolean,string,number,undefined,null,arr,在ts中增加了几种数据类型,比如:元组,枚举,以及数组的不同使用的方法,下面一个一个来看:
首先有一点,ts中语法要求很严格,比如你定义一个字符串类型的变量,就不能再为他赋值为数值类型。再者比如你使用了一个未定义的变量或者方法也会直接报错,确实很规范化啊
1、字符串类型
为变量定义基本数据类型的时候一把都是下面这种格式 变量:=""
let a:string='my name is xiaoming'
console.log(a)
2、number类型
let b:number=13
// b='as' 报错 不能声明为其他类型的
console.log(b)
3、boolean类型
let c:boolean=false
c=true
console.log(c)
4、数组类型
数组类型和普通的数据类型有些区别,有两种定义方法,这里如果定义了number类型,那么就无法再给数组赋值为字符串类型的元素
let d:Array<number>=[1,2,3]
d=[3,4,5]
console.log(d)
let f:number[]=[1,2,3]
console.log(f)
5、元组类型
元组类型有些类似数组类型,是数组类型的扩展,需要注意的是这里定义了几个变量下边就只能写几个元素,如果不符合定义的规范,就会报错
let e:[number,string]
e=[1,'2']
console.log(e[0])
6、枚举类型
枚举类型 enum 这里主要是为了定义一些固定值 比如状态值,产品固定特性等,下面是具体使用的方法。如果枚举对象没有被赋值,那么打印出来的就是该元素的下标。
enum Color {Red='red', Green='green', Blue='blue'};
let g: Color = Color.Blue;
console.log(g); // 输出 blue
7、any类型
any是个什么类型呢,他可以说是一个万能类型,就像原来什么都不写的时候,你无论给他什么类型,都不会出错,下面看看具体使用场景
let msg:any='123'
msg=true
console.log(msg)
可以看到他并没有报错,我们来看看下面的场景
如果你要获取dom元素,那么使用any再合适不过了
8、undefined null类型
let k:undefined
console.log(k)
let l:undefined
l=2 // 这样就会报错
//null同理
let k:null
k=2 //报错
//来看看混合使用的方法
let k:undefined | null | number
k=undefined //不报错
k=null//不报错
k=2//不报错
9、void类型
这是一个奇怪的类型,叫做不返回值类型,什么意思呢,可以简单的理解为这个函数没有做任何实际性的操作,比如下面这样:
function a():void{
console.log("a")
}
//像这样没有任何实际影响的方法就用void
function b():number{
return 123
}
//我们知道ts是非常严格的,会去检查你的代码是否有错,当你上边方法定义的返回数值类型,里边返回的是字符串类型,这样是会报错的。
10、never类型
这种类型主要针对那些无限循环或者抛出异常,无法执行到终点的事件
let x: never;
let y: number;
// 运行错误,数字类型不能转为 never 类型
x = 123;
// 运行正确,never 类型可以赋值给 never类型
x = (()=>{ throw new Error('exception')})();
// 运行正确,never 类型可以赋值给 数字类型
y = (()=>{ throw new Error('exception')})();
// 返回值为 never 的函数可以是抛出异常的情况
function error(message: string): never {
throw new Error(message);
}
// 返回值为 never 的函数可以是无法被执行到的终止点的情况
function loop(): never {
while (true) {}
}
TypeScript函数
在ts中不止定义变量需要写类型,在写函数中参数的时候也需要写类型,可能是为了更加规范化,而且让人看得第一眼就能明白。
可选参数
我们知道老方法写函数的时候,传参没什么具体要求,传两个形参,只有一个实参,或者传两个实参,只有一个形参,这都是可以的。但是在ts中,你必须传相对应的参数,比如定义了一个形参,你就只能传一个实参,或者定义了两个形参,你就不能只传一个实参,看下面例子
function update(name:string,age?:number){
console.log(name)
console.log(age)
}
update('syx') //age后边?代表可选参数,有问号代表可以传可以不传,如果没有就会报错了
function update(name:string,age:number){
console.log(name)
console.log(age)
}
update('syx') //这是错误示例
默认参数
默认参数和可选参数差不多,也是基于规范,传参必须对应。这里有默认age=18,不传参数也是可以的。如果没有默认参数,就会报错。
function init(name:string,age:number=18){
console.log(name)
console.log(age)
}
init('syx')
剩余参数
剩余参数就像es6的参数,用...arg代表,只不过要写上类型
function last(name:string,...arr:number[]){
console.log(name)
let count:number=0
for(var i=0;i<arr.length;i++){
count+=arr[i]
}
return count
}
alert(last('syx',1,2,3))
箭头函数
箭头函数完全就是es6的箭头函数,只是变量要加上类型
var a=(x:number)=>{
return x+10
console.log(x)
}
a(10) //20
函数的重载
在js中我们知道函数是不能重载的,会覆盖原来定义的函数,在ts中可以重载,但是这里具体意义还没搞懂,后边再补上
function freight(str:string):void
function freight(str:number):void
function freight(x:any,y?:any):void{
console.log(x)
console.log(y)
}
freight('syx')
//这里定义了三个同名函数,在使用的时候回自动去匹配应该执行哪个函数。
//转成js代码是这样的
function freight(x, y) {
console.log(x);
console.log(y);
}
freight(123);
Typescript接口
接口用关键字interface来定义,这个接口可不是你平时项目前后端对接所谓的接口,可以理解为定义了一种规范,用来约束你所定义的参数,类,对象等等。
传参约束
对于传参约束,我觉得可以联想到平常项目开发中用到的一些传参规范,比如对接的时候,请求方法的传参,像下面这样在传参的时候将规范定义在后边:params:config
interface config{
interface:string;
data?:string; //问号代表可不传
showLoading:boolean
}
function httpGet(params:config){
if(params.showLoading){
api.showLoading()
}
axios.get(params.interface,params.data,function(){
})
}
//这样我们在传入参数时就必须按照这种规范来传
httpGet({
interface:'http://fy/getNewsList',
showLoading:false,
})
上边定义的config接口,规定了必须要传interface,showLoading这两个参数,而且参数类型必须是string和boolean。data后边有个问号,可以是选填。
这样你就可以在你所有的请求中只要有这种约束,就必须按照这种传参来传,保证了传入参数的正确性。
函数类型接口
这种接口可以用在你定义的方法中,用来约束这个方法的传入值以及返回值
//函数类型接口 对方法传入的参数进行约束,以及返回值进行约束 ???
interface enctypt{
(key:string,value:string):string //代表必须传入key value 返回值必须是string
}
var md5:enctypt=function(key:string,value:string):string{
return key+value
}
var ssh:enctypt=function(key:string):string{
return key
}
console.log(md5('张三','123456'))
console.log(ssh('李四','456789'))
对数组,对象的约束
[index:number]:string代表索引是数字,那么对应的就是数组,因为数组的下标才是数字,string代表这个索引对应的值。
//可索引接口 对数组的约束
interface UserArr{
[index:number]:string
}
var arr:UserArr=['aaa','bbb']
console.log(arr[0])
//对对象的约束
interface obj{
[index:string]:string //[index:string] 代表索引是字符串 代表这是一个对象 后边string代表值
}
var obj:obj={name:'asd',age:13} //错误
类类型接口
类类型接口相当于先定义了一个接口,后边凡是想符合我这个接口的类,就必须按照我这种规范来做。这里用implements关键字来代表符合该接口规范,这种接口有点像类的多态,定义一个方法不去实现,让子类去实现这个方法。
//这里Animal类规定了凡是要用我规范的 就必须有name,eat这两个属性和方法
interface Animal{
name:string;
eat(name:string):void;
}
class Dog implements Animal{
name:string;
constructor(name:string){
this.name=name
}
eat(){
console.log(this.name+'吃骨头')
}
}
class Cat implements Animal{
name:string;
constructor(name:string){
this.name=name
}
eat(param:string){
console.log(this.name+'吃'+param)
}
}
var dog=new Dog('二哈')
var cat =new Cat('杰瑞')
dog.eat()
cat.eat('老鼠')
上述代码表示:构造了Dog和Cat两个类,要求是符合Animal这个接口的规范。可以看到每个类中都有name这个属性,eat这个方法,如果不定义,那么ts在解析时就会报错。
Typescript继承
ts实现构造函数用的是class,就是es6的语法,比如下面这样:
一共分为五步:
1、class构造一个类
2、定义变量类型
3、constructor将变量注册为类的变量,这里要注意类型要一致
4、方法直接写在类里边,定义方法的返回类型
5、实例化一个类,调用方法的时候直接用实例化的类去调用即可
class Person{
name:string;
age:number
constructor(name:string,age:number){
this.name=name
this.age=age
}
getName():void{
alert(this.name)
alert(this.age)
}
setName(name:string):void{
this.name=name
}
}
var person = new Person('张三',18)
alert(person.getName())
ts实现继承用的是关键字 extends
我们实现了一个Person类,这里我们实现一个student类想要继承Person类的方法,也是分为三步走:
1、class 类名 extends 父类名
2、construtor将父类的变量继承下来,这里用extends时候,父类的方法会自动注册到子类的身上,如果子类在一个定义了相同的方法,就会覆盖掉父类身上的这个方法
3、实例化一个对象,调用对象身上的方法
class Student extends Person{
constructor(name:string,age:number){
super(name,age)
}
getName():void{
alert(`${this.name}是一个好孩子`)
}
}
var student=new Student('李四',16)
student.getName() //李四是一个好孩子
类关键字 public private protected
public:定义的变量可以在本身以及继承的子类中使用以及外部使用
private 私有变量 只能在自己类中使用
protected 保护变量 可以在本身及自己的子类中使用
class A{
protected name:string
constructor(name:string){
this.name=name
}
getName():string{
return `${this.name}是共有的变量`
}
}
class B extends A{
constructor(name:string){
super(name)
}
sayName():void{
alert(this.name) //在使用private的时候,这里会报错,因为private只能在本身使用
}
}
var a=new A('小明')
var b=new B('小刚')
alert(a.name) //protected 这里会报错,因为不是在本身也不是在他的子类中使用
alert(b.name)
实现静态方法
首先在es5中实现静态方法我们使用下面这种方式,直接在构造的方法上加一个方法
function Base(name){
//这样在内部定义的就叫实例方法 调用需要先实例化一个对象 然后调用对象的方法
this.name=name
this.sayAge=function(){
alert(this.name)
}
}
//这样的就叫静态方法 调用可以直接 Base.getAge()
Base.getAge=function(){
alert(this.age)
}
在ts中使用static去定义静态变量以及静态方法,在调用的时候就可以不用通过实例化一个对象,而是直接去调用。
class Per{
public name:string;
public age:number;
static sex:string;
constructor(name:string,age:number){
this.name=name;
this.age=age
}
getName():void{
alert(this.name)
}
static getSex():void{
alert(this.sex)
}
}
//静态方法通过static定义 并且可以直接通过类去调用
Per.getSex()
多态
父类定义的方法不去实现 让他的子类去实现 每一个子类有不同的表现
//多态 父类定义的方法不去实现 让他的子类去实现 每一个子类有不同的表现
class Animal{
name:string;
constructor(name:string){
this.name=name
}
eat(){
}
}
class Dog extends Animal{
constructor(name:string){
super(name)
}
eat(){
alert(`${this.name}爱吃肉`)
}
}
class Cat extends Animal{
constructor(name:string){
super(name)
}
eat(){
alert(`${this.name}爱吃鱼`)
}
}
var dog=new Dog('二哈')
var cat=new Cat('杰斯')
dog.eat()
cat.eat()
我们先定义了一个Animal类,在类中定义了一个eat方法,但是我们没有去实现这个方法,而是让子类去实现,每一个子类有不同的变现,从而实现了类的多态性
抽象
抽象类是给其他类提供基准的基类,不能被直接实例化
抽象 anstract 用abstract关键字定义抽象类和抽象方法,抽象类中抽象方法不包含具体的实>现,必须在他的派生类中实现 换句话说 就是在子类中实现
抽象方法只能放在抽象类中 凡是继承了抽象类的子类 必须包含父类中抽象的方法
抽象就是为子类定义一个基准
abstract class Animal{
name:string;
constructor(name:string){
this.name=name
}
abstract eat():any
}
// var animal=new Animal() //不能创建抽象类的实例
class Dog extends Animal{
constructor(name:string){
super(name)
}
eat(){
alert(`${this.name}爱吃肉`)
}
}
class Cat extends Animal{
constructor(name:string){
super(name)
}
eat(){
alert(`${this.name}爱吃鱼`)
}
}
这里如果子类中没有实现父类的抽象方法,就会报错,并提示抽象类不能被继承,所以我们在写字类继承抽象类的时候,必须实现父类中的抽象方法。
Typescript泛型
泛型:泛型就是解决 类 接口 方法的复用性、以及对不特定数据类型的支持
实现方法:function method<T>(value:T):T{} 这个T只是用法最多的,但不是写死的,你想用A,B什么来代替也一样
为什么说解决复用性?大家想一下,如果我们要定义一个方法,但是要约束传入的参数和返回的类型一致,也就是说,我传入参数1,要返回number类型,传入‘张三’,要返回string类型,那么现在是不是要写两个方法分别来约束,这就造成了冗余,为了解决这个问题,我们可以使用泛型来定义约束
1、泛型函数
//旧方法约束传参和返回类型保持一致
function getData(value:number):Number{
return value
}
function getData_c(value:string):string{
return value
}
//泛型解决传参和返回类型一致
function getData<T>(value:T):T{
return value
}
console.log(getData<number>(123)) //<number>传入number 那么value必须为number 传入string时就会报错
console.log(getData<string>('123'))
2、泛型类
实现一个类 来找到数组中的最小值,数组元素类型不定
class minCount{
public list:number[]=[];
add(value:number):void{
this.list.push(value)
}
min():number{
let minNum=this.list[0]
for(var i=0;i<this.list.length;i++){
if(this.list[i]<minNum){
minNum=this.list[i]
}
}
return minNum
}
}
var a=new minCount()
a.add(1)
a.add(2)
a.add(3)
alert(a.min())
这种方法弊端:辛辛苦苦写了一个类,最终只能比较数字类型,要是想比较字符串类型还要重构,这也太麻烦了,下面看改造成泛型的类
class minCount<T>{
public list:T[]=[]
add(value:T){
this.list.push(value)
}
min():T{
let minNum=this.list[0]
for(var i=0;i<this.list.length;i++){
if(this.list[i]<minNum){
minNum=this.list[i]
}
}
return minNum
}
}
var a=new minCount<number>()
a.add(1)
a.add(2)
a.add(3)
// a.add('1') //错误 不能赋值给number类型
var b=new minCount<string>()
b.add('a')
b.add('b')
b.add('c')
看下泛型都是用在哪些地方
1、类的后边
2、数组类型
3、add方法的传参
4、min方法的返回值
这四处既能够很好的保证你传入的参数和返回参数一致,又能降低冗余,起到了限制的作用和不确定数据类型的约束
3、泛型接口
interface config{
<T>(value:T):T;
}
var getYear:config=function<T>(value:T):T{
return value
}
var c=getYear<string>('张三') //正确
// var c=getYear<number>('张三') //错误
4、类作为参数约束传入数据的类型
这个举例子来说可能更明了一些:假设你想定义一个方法,这个方法是往数据库传入数据的,但是你要做一个约束,不能让非法类型的数据传入数据库。
比如定义一个用户类,要求传入用户名和密码,这时候如果传入的是性别或者其他属性,肯定是不行的。这时候就要用到类作为参数来约束传入数据的类型,可以理解为接口的特性,就是起到约束的作用。
然后你还有一个文章类,你要传入title,desc,type等属性,这时候如果传入的参数是年龄,大小这些属性肯定也是不行的,这时候你就要用到文章类来约束传入的参数。下面看具体的例子
class User{
username:string | undefined;
password:string | undefined;
}
class Artical{
title:string | undefined;
desc:string | undefined;
type:string | undefined
}
class mysqlDB{
//定义一个方法 传入的参数要遵从User类
add(value:User):boolean{
console.log(value)
return true
}
}
//这样定义的类如果想要约束多个类的规范 就必须重复的写多个 这时候使用泛型 就可以避免重复了
class mysqlDB_c{
//定义一个方法 传入的参数要遵从User类
add(value:Artical):boolean{
console.log(value)
return true
}
}
var u=new User()
// u.username='张三'
u.password='123445'
var params={
name:'09'
}
var DB=new mysqlDB()
var DB_C=new mysqlDB_C()
// DB.add(params) //错误 这里的params不符合user类的规范
// DB.add(u)
var artical=new Artical()
artical.title='学习'
artical.desc='学习让人进步'
DB_C.add(artical)
这样虽然可以实现约束效果,但是如果还有更多的类约束,那么岂不是要写很多的mysqlDB类,这样显然不可行,下面看使用泛型来解决这个问题。
class User{
username:string | undefined;
password:string | undefined;
}
class Artical{
title:string | undefined;
desc:string | undefined;
type:string | undefined;
constructor(params:{ //这里再次进行传入参数约束 必须穿入title,desc type 可不传
title:string | undefined;
desc:string | undefined;
type?:string | undefined;
}){
this.title=params.title,
this.desc=params.desc,
this.type=params.type
}
}
//这样定义一个泛型类 在实例化的时候可以传入约束类 避免了重复构造mysqlDB类
class mysqlDB<T>{
add(value:T):boolean{
console.log(value)
return true
}
update(value:T,id:number):boolean{
console.log(value,id)
return true
}
}
var u=new User()
u.username='张三'
var a=new Artical({
title:'好好学习',
desc:'天天向上'
})
var db=new mysqlDB<User>()
db.add(u)
var db1=new mysqlDB<Artical>()
db1.add(a)
db1.update(a,12)
这里定义user类和artical类的时候有些区别,artical类直接在内部实现了constructor,这样就可以直接在实例化的时候传参,而且可以看到artical类在构造的时候也是进行了约束的,这样约束之后在传参的时候就必须传入title,desc参数,type参数是可选的
mysqlDB类在定义的时候使用了泛型的方法,<T>确保了实例化对象的时候使用了哪种类约束。使用了哪种规范就必须按照这种方式来传参了。