一:typescript介绍和环境搭建
1.typescript介绍
TS是属于JavaScript的超集,可以编译成纯JavaScript,TS新增了许多特性,例如数据类型、类、继承以及接口等。
2,typescript环境搭建
i.项目环境搭建
安装node.js=>全局按照typescript:npm i -g typescript
ii.编写一个ts文件并编译
编写一个.ts文件=>命令行tsc b.ts=>编译成一个JavaScript文件。
iii.vscode配置自动编译
在当前项目执行指令
tsc --init
在项目根目录生成一个tsconfig.json文件:
自动编译,选择终端>运行任务>监视typescript。之后每次文件一保存,就会自动编译。
二:typescript的数据类型
typescript的数据类型主要有:
布尔类型(boolean)
数字类型(number)
字符串类型(string)
数组类型(array)
元组类型(tuple)
枚举类型(enum)
任意类型(any)
null和undefined
void类型
never类型
格式:let 变量:类型=变量值
1,boolean类型
let flag:boolean=[false|true]
2.数字类型
let num:number=12
let float:number=12.12
3.字符串类型
let str:string='hello world'
4.数组类型
let array:number[]=[1,2] let str:string[]=['1','2']
泛型方式
let arr:Array<string>=['we','eo']
5.元组类型
元组类型是数组类型的一种,可以指定数组中每个元素的类型
let tup:[string,number,boolean]=['we',21,false]
6.枚举类型
定义:
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
7.任意类型(any)
在我们获取dom节点的时候,不知道应该取什么类型,就可以使用any类型
let dom:any=document.getElementById('app')
8.null和undefined
i.undefined
在typescript必须指定其变量类型,要不然会报错,这时候我们只定义不赋值的时候,在使用变量的时候变量也会报错
这时候就提供了undefined解决这个问题,指定变量为其他类型或者undefined,这时不赋值就不会报错了。
ii.null
表示为空,和undefined有点类似,一个变量可能是空或者undefined
let num:number|undefined|null
iii.void类型
表示定义无返回值的方法
function fn():void{
console.log('q');
}
相对的可以指定返回值的类型
function fn():number{
return 1
}
9.never类型
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
三.typescript函数
1,typescript函数的定义
有返回值
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 {
}
四.typescript的类、接口
对象:具体的,实际的,代表一个事物,建一个对象的属性和行为封装在一起,就构成一个类。
1.typescript定义类
使用的是es6的新特性:关键字class
class Person{
}
2.类中的属性
类中有三大属性:变量、构造方法、方法。
其中变量又可以分为:公共变量、私有变量、保护变量
class Person{
public name:string;//如果是公共方法,public可以省略
constructor(name:string){
this.name=name
}
eat(){
console.log('普通方法');
}
}
i.实例化类
var person=new Person('小明')
调用:
person.eat() person.name//使用public修饰,可以直接访问,如果使用private修饰,只能向外提供公共的访问方法get/set方法
ii.private关键字:静态变量
特点:静态变量不可以直接调用,只能通过提供对外访问方法,
好处:保证了类中变量的安全性,不使外部直接访问到。
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("小红")
3.类实行继承:extends
继承只能继承父类的公共属性,私有属性无法继承。
继承只能单继承,不能多继承。
类只能单继承。
4.类的修饰符
typescript提供了三个修饰符:public(保护)、protected(保护)、private(私有)
默认是public,定义时可以省略。
protected:被修饰的属性只能在继承体系内使用。
private:定义的属性只能在该类使用。
5.类的静态属性和静态方法:关键字static
在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()
6.类的多态
多态:同一个事物表现出不同的状态表示多态。多态中的父类方法值定义不实现功能。
例如:水有固态、液态和气态三种状态,那么父类就是水,而子类就是不同状态的水。
那就实现以下这个代码吧
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()
7.抽象类:abstract
抽象类就是类的基类
举个例子:人都要吃饭,都要睡觉,都要呼吸,将吃饭睡觉呼吸提取出来放到一个类,并有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()
五.接口:interface
typescript中接口就是对json数据进行约束或者对类进行拓展,约束json数据的时候主要是对对象数据进行约束,规范对象数据的内部数据格式必须和接口一致。
1.接口的约束作用
a.接口对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。
还有一个方式是:(可看对数组的约束,有讲解)
b.接口对函数进行约束
接口对函数进行约束主要是对函数的参数和返回值进行约束
格式:
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));
c.接口对数组(或对象)的约束
格式:
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);
4,接口对类的拓展
和抽象类有点类似,如果把类比作一个人的话,把人共有的提取出来,封装成一个抽象类;往人身上添加东西的就是接口。
比如张三是一个人,是个人就要继承人的抽象类(不吃饭、不睡觉不呼吸就不是人了);张三这个人追求个性,所以他实现了接口(红眉毛、绿口红才有个性)。这时候王五也是个人,也需要继承人这个抽象类,但是他不追求个性,所以他不实现这个接口。
类实现接口可以这么理解!!!
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());
5.注释
类可以同时继承一个类和实现多个接口
六.泛型
泛型:泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。.泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
1.函数泛型
有这么一个需求,一个函数可以返回任意类型,可以使用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类型。
2.泛型类
有这么一个需求,定义一个类,该类中功能是添加数组元素。要求只能添加同一个数据类型的数据。
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());
3.泛型接口
i.泛型接口之函数泛型接口
顾名思义,就是把泛型加载接口中的函数上
interface DataInterface{
<T>(str:T):T
}
var getData:DataInterface=function<T>(name:T):T{
return name
}
console.log(getData("xiaozhi"));
ii.泛型接口之接口泛型接口
顾名思义,就是将泛型定义在接口上。
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("小白"));
七.typescript模块化
使用了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文件
1.命名空间
在写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,
1.无参类装饰器
无参装饰器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()
2.有参装饰器:返回一个函数
有参装饰器的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()
3.属性装饰器
属性装饰器返回的函数多出了一个,表示该属性
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);
4.方法装饰器
方法装饰器的作用可以用来监视,修改或者替换方法的定义
方法装饰器运行的时候需要传入三个参数
对于静态成员来说是类的构造函数,对于实例成员是类的原型
成员的名字
成员的属性描述符号
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就是该方法本身
a,使用方法装饰器替换方法
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")
5.方法参数装饰器
作用:当函数被调用的时候,使用方法装饰器向类原型添加一些属性或者方法等。
格式:
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);
6.装饰器的执行顺序
属性装饰器>方法装饰器>方法参数装饰器>类装饰器
如果同种装饰器有多个,执行顺序从下到上,右到左。