TS是属于JavaScript的超集,可以编译成纯JavaScript,TS新增了许多特性,例如数据类型、类、继承以及接口等。
安装node.js=>全局按照typescript:npm i -g typescript
编写一个.ts文件=>命令行tsc b.ts=>编译成一个JavaScript文件。
在当前项目执行指令
tsc --init
在项目根目录生成一个tsconfig.json文件:
自动编译,选择终端>运行任务>监视typescript。之后每次文件一保存,就会自动编译。
typescript的数据类型主要有:
布尔类型(boolean)
数字类型(number)
字符串类型(string)
数组类型(array)
元组类型(tuple)
枚举类型(enum)
任意类型(any)
null和undefined
void类型
never类型
格式:let 变量:类型=变量值
let flag:boolean=[false|true]
let num:number=12
let float:number=12.12
let str:string='hello world'
let array:number[]=[1,2] let str:string[]=['1','2']
泛型方式
let arr:Array<string>=['we','eo']
元组类型是数组类型的一种,可以指定数组中每个元素的类型
let tup:[string,number,boolean]=['we',21,false]
定义:
enum 枚举变量名{ 枚举类名=枚举值, 枚举类名1=枚举值1, ......... }
使用
var 变量:枚举类型=枚举变量名.枚举名
或
var 变量名=枚举变量名.枚举名
enum flag{ success=1, errorM=-1 } var F:flag=flag.success //var F=flag.success console.log(F);
单枚举项都为number类型的时候(默认为number)如果不赋值,打印的将是元素的索引值,如果元素的前面的元素有值,则改元素打印的值为前面元素加1
在我们获取dom节点的时候,不知道应该取什么类型,就可以使用any类型
let dom:any=document.getElementById('app')
在typescript必须指定其变量类型,要不然会报错,这时候我们只定义不赋值的时候,在使用变量的时候变量也会报错
这时候就提供了undefined解决这个问题,指定变量为其他类型或者undefined,这时不赋值就不会报错了。
表示为空,和undefined有点类似,一个变量可能是空或者undefined
let num:number|undefined|null
表示定义无返回值的方法
function fn():void{ console.log('q'); }
相对的可以指定返回值的类型
function fn():number{ return 1 }
never表示其他类型的(包括null和underfined)子类型,表示从未出现过的值,是一个隐含的类型。主要体现在
never类型只能被never类型赋值
let num:number; num=12
如上声明num类型的时候,该变量赋值时只能赋一个number类型的值。如果不是会报错
let nev:never nev:(()=>{ throw new Error('错误') })()
真正的写法如上,但是不常用,一般使用any或者多类型定义来解决。
let a:null|undefined|number|string|boolean
有返回值
function name(params:type):type { return paramType } 或 let name=function (params:type):type { return paramType }
无返回值
function name(params:type):void { } 或 let name=function (params:type):void { }
对象:具体的,实际的,代表一个事物,建一个对象的属性和行为封装在一起,就构成一个类。
使用的是es6的新特性:关键字class
class Person{ }
类中有三大属性:变量、构造方法、方法。
其中变量又可以分为:公共变量、私有变量、保护变量
class Person{ public name:string;//如果是公共方法,public可以省略 constructor(name:string){ this.name=name } eat(){ console.log('普通方法'); } }
var person=new Person('小明')
调用:
person.eat() person.name//使用public修饰,可以直接访问,如果使用private修饰,只能向外提供公共的访问方法get/set方法
特点:静态变量不可以直接调用,只能通过提供对外访问方法,
好处:保证了类中变量的安全性,不使外部直接访问到。
class Person{ private name:string; constructor(name:string){ this.name=name } eat(){ console.log('普通方法'); } getName(){ return this.name } setName(name:string){ this.name=name } } var p =new person()
直接通过:p.name是无法访问该变量,只能通过getName/setName间接访问和修改
p.getName()
p.setName("小红")
继承只能继承父类的公共属性,私有属性无法继承。
继承只能单继承,不能多继承。
类只能单继承。
typescript提供了三个修饰符:public(保护)、protected(保护)、private(私有)
默认是public,定义时可以省略。
protected:被修饰的属性只能在继承体系内使用。
private:定义的属性只能在该类使用。
在es5中,直接通过构造函数的方式添加方法和属性,叫做静态方法和静态属性
function Person(){ name:"xiaozhi" setName:()=>{ console.log("实例方法") } } Person.age="12" Person.setAge=function(){ console.log('静态方法'); } //静态调用 Person.setAge() Person.age //实例调用 var p=new Person() p.setName() p.name
typescript中的静态方法和静态实例
静态方法和属性和实例方法和属性的区别
静态方法和实例可以直接通过类名调用,实例方法和属性需要使用实例化类名调用。
静态方法和属性不会随着类的调用后而消失,实例方法和属性则会。
定义静态属性和静态方法
class Person{ static name2='xiaozhi'; static run(){ console.log("go"); } } Person.name2 Person.run()
多态:同一个事物表现出不同的状态表示多态。多态中的父类方法值定义不实现功能。
例如:水有固态、液态和气态三种状态,那么父类就是水,而子类就是不同状态的水。
那就实现以下这个代码吧
class water{ name:string; constructor(name:string){ this.name=name } waterState(){} } class Gutai extends water{ constructor(name:string){ super(name) } waterState(){ console.log("我是"+this.name+"水"); } } class Yetai extends water{ constructor(name:string){ super(name) } waterState(){ console.log("我是"+this.name+"水"); } } class Qitai extends water{ constructor(name:string){ super(name) } waterState(){ console.log("我是"+this.name+"水"); } } var gutai=new Gutai('固态') gutai.waterState() var yetai=new Yetai('液态') yetai.waterState() var qitai=new Qitai('气态') qitai.waterState()
抽象类就是类的基类
举个例子:人都要吃饭,都要睡觉,都要呼吸,将吃饭睡觉呼吸提取出来放到一个类,并有abstract修饰,就变成一个抽象类了,只是抽象类只提供行为不执行行为,你吃什么饭,睡觉睡得怎样呼吸快慢抽象类不管,抽象类只管你要是继承我,这些行为必须得有。
抽象类有一下特性:
抽象类不能被实例化,
抽象类的成员可以不是抽象成员,但是有抽象成员的类必须是抽象类。
子类继承抽象类必须重写抽象类的抽象方法。
子类可以是一个抽象类,表示该子类必须有这个行为。
总结一下:抽象类就是提供行为而不执行行为的类【人是一个抽象类,都得吃饭,小米是一个具体类(也可以是抽象子类,这个看下面)吃什么不管,你想吃啥就吃啥,但是必须得有吃的这个行为】。
abstract class Person{ abstract eat():any; } class Xiaoming extends Person{ food:string; constructor(food:string){ super() this.food=food } eat(){ console.log("小明会吃,正在吃"+this.food); } } var xiaoming=new Xiaoming("窝窝头") xiaoming.eat()
抽象类的成员:
成员变量:既可以是变量也可以常量,当不能用abstract修饰(修饰就无法初始化变量了)
成员方法:既可以是一般方法也可以是抽象方法,一般方法无需重写(想要访问抽象类的变量最好重修)
构造函数:有,作用是初始化成员变量。
abstract class Person{ food:string; constructor(food:string){ this.food=food } abstract eat():any; run(){ console.log("小明边跑边吃着"+this.food); } } class Xiaoming extends Person{ constructor(food:string){ super(food) } eat(){ console.log("小明会吃,正在吃"+this.food); } } var xiaoming=new Xiaoming("大白菜") xiaoming.eat() xiaoming.run()
typescript中接口就是对json数据进行约束或者对类进行拓展,约束json数据的时候主要是对对象数据进行约束,规范对象数据的内部数据格式必须和接口一致。
interface PersonIf{ name:string; age:number; sex:boolean } function getPerson(obj:PersonIf):string{ return '' } var obejct={ name:'小孩', age:18, sex:false } getPerson(obejct)
解读:以上代码的getPerson函数接收一个类型为PersonIf的对象,PersonIf则是约束了转入对象参数的格式的接口,即obejct对象必须要有和接口一样的属性,要会报错。
值得注意的是:接口有的属性,对象必须要有;对象有的属性,接口不一定强制有;
interface PersonIf{ name:string; age:number; sex:boolean } function getPerson(obj:PersonIf):string{ return return obj.name+obj.age } var obejct={ name:'小孩', age:18, } getPerson(obejct)
一般情况下,后端接口传过来的json数据可能会根据请求参数的不同,传到前端的数据可以字段也会不太一致,或者有些什么直接参数丢失等。这时候,我们可以把interface的约束属性变成一个可选值。使用“?”符号
此时接口有的属性,对象中不一定要有了
interface PersonIf{ name?:string; age?:number; } function getPerson(obj:PersonIf):any{ return obj.name } var obj={ name:'小孩', } console.log(getPerson(obj));
值得注意的是:可选参数的时候,有返回值,方法定义的放回类型应该是一个any,因为不知道这个name是可选的,有可能是一个underfined。
还有一个方式是:(可看对数组的约束,有讲解)
接口对函数进行约束主要是对函数的参数和返回值进行约束
格式:
interface 接口名{ (参数名1:参数类型1,参数名2:参数类型2,...):返回值类型 }
interface PersonIf{ (name:string,age:number):string } let fn:PersonIf=function(name:string,age:number):string{ return name+age } console.log(fn('xiaozhi',18));
格式:
interface 接口名{ [index:number]:数组元素类型 }
如果是数组,index的类型必须是number,如果是对象,可以是string(不常用,原因是对象的属性很多情况类型是不同的,可以使用any解决)
约束数组代码:
interface array{ [index:number]:string } let arr:array=['12','34'] console.log(arr);
约束对象代码:
interface obj{ [index:string]:any } let duixiang:obj={ name:'xiaozhi', sex:18, jop:{ p1:'老师', p2:'家长' } } console.log(duixiang.jop.p1,duixiang.name);
和抽象类有点类似,如果把类比作一个人的话,把人共有的提取出来,封装成一个抽象类;往人身上添加东西的就是接口。
比如张三是一个人,是个人就要继承人的抽象类(不吃饭、不睡觉不呼吸就不是人了);张三这个人追求个性,所以他实现了接口(红眉毛、绿口红才有个性)。这时候王五也是个人,也需要继承人这个抽象类,但是他不追求个性,所以他不实现这个接口。
类实现接口可以这么理解!!!
interface PersonIf{ name:string; age:number; kouhong():any; } class person implements PersonIf{ name:string; age:number; constructor(name:string,age:number){ this.name=name this.age=age } kouhong(){ console.log(this.name+'今年'+this.age+'岁,所以涂口红'); } } var p=new person("张三",18) console.log(p.kouhong());
注意:接口中的方法只能定义不能有方法体;类实现接口必须重写接口内的全部属性和方法;接口可以多实现;接口和接口可以实现继承(一般使用多实现就可以了,没必要继承)
interface PersonIf1{ id:string age:number; kouhong():any; } interface PersonIf2{ name:string; meimao():any; } class person implements PersonIf1,PersonIf2{ id:string; name:string; age:number; constructor(name:string,age:number,id:string){ this.name=name this.age=age this.id=id } kouhong(){ console.log(this.name+'今年'+this.age+'岁,所以涂口红'); } meimao(){ console.log("眉毛"); } } var p=new person("张三",18,'11111') console.log(p.kouhong());
类可以同时继承一个类和实现多个接口
泛型:泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。.泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
有这么一个需求,一个函数可以返回任意类型,可以使用any,但是使用any异味这放弃了类型检查,这时候就需要泛型来解决,使其传入什么类型就放回什么类型。
泛型是一个参数化类型,可以检查参数列表的合法性。
function fn<T>(name:T):T{ return name } //这种调用方式相当any //console.log( fn('小智')); //console.log( fn(12)); //console.log( fn(false)); console.log( fn<string>('小智')); console.log( fn<number>(12)); console.log( fn<boolean>(false));
上面的代码中,<T>表示泛型,T表示泛型类型名称(传入什么就是什么),这里的泛型类型也可以是typescript中的某个数据类型。在这个代码中,返回的是泛型类型,我们也可以指定返回的类型(不是泛型)
function fn<Y>(name:Y):string{ return 'CODD' } console.log( fn<string>('小智'));
但是以下代码是错误的
function fn<Y>(name:Y):string{ return name } console.log( fn<string>('小智'));
因为返回的是name,而name的类型不一定是string类型,有可能是number类型。
有这么一个需求,定义一个类,该类中功能是添加数组元素。要求只能添加同一个数据类型的数据。
class AddArray<T>{ array:T[]=[]; add(item:T):void{ this.array.push(item) } getArray():any{ return this.array } } var m=new AddArray<string>() m.add('xiozhi') m.add('xiaolei') m.getArray() console.log(m.getArray()); var m2=new AddArray<number>() m2.add(12) m2.add(13) console.log(m2.getArray());
顾名思义,就是把泛型加载接口中的函数上
interface DataInterface{ <T>(str:T):T } var getData:DataInterface=function<T>(name:T):T{ return name } console.log(getData("xiaozhi"));
顾名思义,就是将泛型定义在接口上。
interface DataInterface<T>{ (str:T):T } function fn<T>(name:T):T{ return name } var getData:DataInterface<string>=fn var getData1:DataInterface<number>=fn console.log(getData("小白")); console.log(getData1(12));
在定义的时候就直接指定泛型类型,简化写法(函数只调用一次)
interface DataInterface<T>{ (str:T):T } var getData:DataInterface<string>=function<T>(name:T):T{ return name } console.log(getData("小白"));
使用了JavaScript模块化方式,使用export向外暴露模块内容,使用import引入暴露出来的数据。
新建如下文件
每个文件中写入:
user.ts
import userImp from './userImp' class User implements userImp{ name:string age:number constructor(name:string,age:number){ this.name=name this.age=age } getName():string|undefined{ return this.name } setName(name:string):void{ this.name=this.name } getAge():number|undefined{ return this.age } setAge(age:number){ this.age=age } } export default User
userImp.ts
interface userImp{ name:string age:number } export default userImp
index.ts
import User from './user' var user= new User('XIAOHZI',18) console.log(user.age); console.log(user.name);
由于模块化编程是编译成的js无法被浏览器运行,这时候需要通过node来运行jindex.js文件
在写typescript的时候,定义类或者变量多的时候,可能会导致命名冲突,这时候我们可以使用命名空间来把代码分开,格局命名冲突。
namespace 空间名{........}
在命名空间内需要通过export将需要导出的内容导出就可以了。
namespace A{ export class a{ name:string|undefined getName():any{ console.log(this.name); return this.name } } } namespace B{ export class a{ name:string|undefined getName():any{ console.log(this.name); return this.name } } } var Aa=new A.a() var Ba=new B.a
如果想要将命名空间模块化,也需要使用export将空间导出
export namespace A{ export class a{ name:string|undefined getName():any{ console.log(this.name); return this.name } } }
装饰器是一个方法,可以向一个类、方法、属性、参数注入内容,用于扩展其功能。
使用方式
@装饰器名(param:any){...}
接收一个参数,这个参数表示的是当前类、方法、属性或者参数。
主要:要支持装饰器,需要在ts配置文件中打开:
"experimentalDecorators": true,
无参装饰器params就是类本身
function logDom(params:any){ //param表示的是类本身 // 向当前类添加属性 params.protoType.age='12' // 向类里面添加方法 params.protoType.getAge=function(){ console.log("11"); } } @logDom class Dome{ constructor(){} show():void{ console.log("累呀"); } } var dome:any=new Dome() console.log(dome.age); dome.getAge()
有参装饰器的params表示的是传入的参数,target表示类本身。
function logDom(params:any){ //param表示的是类本身 // 向当前类添加属性 console.log(params) return function(target:any){ target.protoType.age='12' // 向类里面添加方法 target.protoType.getAge=function(){ console.log("11"); } } } @logDom("你好") class Dome{ constructor(){} show():void{ console.log("累呀"); } } var dome:any=new Dome() console.log(dome.age); dome.getAge()
类装饰器还可以通过重载的方式修改累的构造函数和方法
function decorateDome(target:any){ return class extends target{ url:any='你好' getUrl(){ console.log("不好的"); } } } @decorateDome class Dome{ url:string|undefined; constructor(){ this.url="我是一个url" } getUrl(){ console.log(this.url); } } var dome=new Dome() console.log(dome.url,); dome.getUrl()
属性装饰器返回的函数多出了一个,表示该属性
function decorateDome(params:any){ return function(target:any,atrr:any){ target[atrr]=params } } class Dome{ @decorateDome("属性装饰器") url:string|undefined; constructor(){ // this.url="我是一个url" } getUrl(){ console.log(this.url); } } var dome=new Dome() console.log(dome.url);
方法装饰器的作用可以用来监视,修改或者替换方法的定义
方法装饰器运行的时候需要传入三个参数
对于静态成员来说是类的构造函数,对于实例成员是类的原型
成员的名字
成员的属性描述符号
function decorateDome(params:any){ return function(target:any,methodName:any,desc:any){ console.log(params); console.log(target); console.log(methodName); console.log(desc); } } class Dome{ url:string|undefined; constructor(){ // this.url="我是一个url" } @decorateDome("属性装饰器") getUrl(){ console.log(this.url); } } var dome=new Dome()
可以看到target就是类的本身,methodName是方法名,desc是对这个方法的表述,而value就是该方法本身
function decorateDome(params:any){ return function(target:any,methodName:any,desc:any){ desc.value=function(...args:any[]){ console.log(args); } } } class Dome{ url:string|undefined; constructor(){ } @decorateDome("属性装饰器") getUrl(){ console.log(this.url); } } var dome=new Dome() dome.getUrl("你好","haha")
这时候我们可以看到该方法以及被替换了,但是在很多情况下,我们是需要修改这个方法,而不是替换,这时候可以使用apply实现函数的修改
function decorateDome(params:any){ return function(target:any,methodName:any,desc:any){ var cMethod=desc.value desc.value=function(...args:any[]){ console.log(args); cMethod.apply(target,args) } } } class Dome{ url:string|undefined; constructor(){ // this.url="我是一个url" } @decorateDome("属性装饰器") getUrl(){ console.log("我感觉还行"); } } //var dome=new Dome() var dome:any=new Dome() dome.getUrl("你好","haha")
作用:当函数被调用的时候,使用方法装饰器向类原型添加一些属性或者方法等。
格式:
return function(target:any,methodName:any,paramsIndex:any){ ... }
paramsIndex:表示该参数的索引
function decorateDome(params:any){ return function(target:any,methodName:any,paramsIndex:any){ console.log(paramsIndex); target.apiUrl="哈哈哈" } } class Dome{ url:string|undefined; constructor(){ // this.url="我是一个url" } getUrl(@decorateDome("hello") name:any){ } } var dome:any=new Dome() dome.getUrl("你好") console.log(dome.apiUrl);
属性装饰器>方法装饰器>方法参数装饰器>类装饰器
如果同种装饰器有多个,执行顺序从下到上,右到左。