TypeScript超详细教程
前言
TypeScript
是 JavaScript
的一个超集,支持 ECMAScript 6
标准
可以理解成TypeScript
是高版本的JS
中文学习网址
安装TypeScript
npm install -g typescript
编写第一个TS
注意此时文件的后缀不再是
js
而是ts
function hello(){
let str:string = "Hello Word!!";
console.log(str)
}
hello();
然后执行命令
tsc ***.ts
执行结束后 会转为js文件 然后我们在执行js文件
这一系列操作下来太麻烦了,我们直接一步到位
安装便捷编译
npm i ts-node -g
执行当前命令会直接输出
ts-node ***.ts
定义变量
在ts中一旦定义了一个变量可以指定一个类型。
基础静态类型:
number、string、void、null、undefined、boolean、symbol、、、
一旦定义了类型,那么在重新赋值必须是和第一次赋值一样的类型,即使你在定义变量的时候不指定类型
let a : number = 1;
let b : string = "1";
b = 1;//报错字符串数字和number类型数字也是不能直接赋值的
let c = false;//在ts中这也是可法的,在定义的时候你可以不用指定类型
c = "false";//报错因为当前c变量在第一次赋值是属于boolean类型你不可改变该类型
c = true;//可法
c = 0;//不可法
c = !0;//可法,你可以赋值一个逻辑运算
基础对象类型定义
let object : {
name : string,
age : number
} = {
name : "测试基础对象定义",
age : 1
}
object = {
name :"减少、增加属性",
age : 222,
adress : "测试"
}//报错
基础数组类型定义
let any : string [];
any = ["a","b","c"]
any = ["1","2",3]//报错
let any1 :number[] = [1,2,3];
复杂多类型数组定义
let any :(number|string)[] = [1,"a","b",2]
利用接口
interface
定义数组
//其实一个普通的数组转为对象
let a = [1,2,3];
其实他的对象无法类似就是{0:1,1:2,2:3}
interface List{
[index:number]:number;//这里的index指向的是数组下标
}
let list : List = [1,2,3]
定义函数
//定义一个没有返回值的函数
let doSomething : ()=>void=()=>{};
//返回字符串的函数
let doSomething1 : ()=>string = ()=>"1"
//返回数字类型的函数
let doSomething2 : ()=>number= ()=>1
//返回一个对象
let doSomething2 :()=>Person = ()=>new Person();
定义一个自定义类型
关键字inteface
interface Person {
userName : string,
age : number
}
let xiaoming : Person = {
userName :"小明",
age : 20
}
可选参数、多传参数定义
//可选参数
function test(a:string,b?:string):void{};
function test1(a:string){}
test1("a","b","c");//会报错
function test1(a:string,...args:string[]){}
函数的返回类型注解
//返回值类型注明是string
function doSomething(name:string):string{
return "你好啊" + name;
}
//没有返回值
//或者直接可以不写void
function doSomething(name:string):void{
return "你好啊" + name;
}
let doSomething3 : Function = ():string=>"1"
函数返回类型有一个特殊的类型
never
那么什么是never
?
如果某个函数是never
的返回类型,那么代表这个函数被阻断了,不能完全执行完成。
let neverFn = ():never=>{
console.log(111);
while(true){}//死循环
console.log(222);//不能执行到这里
}
let neverFn = ():never=>{
console.log(111);
throw new Error();
console.log(222);//不能执行到这里
}
元祖
元祖的定义其实就和数组差不多
let any : [string,string,number] = ["a","b",1]
粗看感觉和普通的多类型数组定义差不多,但是现在类型枚举在括号内,而且每一项必须对应,不能少也不能多,也就是第一项你定义了string
类型,那么你数据的第一项必须是string
类型,而且有多少值你得定义多少类型,看起来很鸡肋
接口
interface
关键字定义一个接口
基础定义接口
interface Person{
name:string;
age:number;
say:()=>void
}
let xiaoming:Person = {
name:"小明",
age:21,
say:()=>`HI I am xiaoming`
}
console.log(xiaoming.name);
console.log(xiaoming.age);
console.log(xiaoming.say());
任意类型any
interface Person{
name:string;
age:number;
like:any;
say:()=>void
}
let xiaoming:Person = {
name:"小明",
age:21,
like:"游泳",
say:()=>`HI I am xiaoming`
}
console.log(xiaoming.name);
console.log(xiaoming.age);
console.log(xiaoming.say());
可有可无属性?
interface Person{
name:string;
age:number;
like:any;
say:()=>void;
desc?:any;
}
let xiaoming:Person = {
name:"小明",
age:21,
like:"游泳",
say:()=>`HI I am xiaoming`
}
console.log(xiaoming.name);
console.log(xiaoming.age);
console.log(xiaoming.say());
联合属性 以|
分割属性
interface Person{
name:string;
age:number;
like:any;
say:()=>void;
desc?:any;
adress:string|string[]|(()=>string);
}
let xiaoming:Person = {
name:"小明",
age:21,
like:"游泳",
say:()=>`HI I am xiaoming`,
adress:"艾欧尼亚"
}
console.log(xiaoming.name);
console.log(xiaoming.age);
console.log(xiaoming.say());
类的接口定义
interface Person{
name:string;
age:number;
like:any;
say:()=>void;
adress:string|string[]|(()=>string);
}
let xiaoming:Person = {
name:"小明",
age:21,
like:"游泳",
say:()=>`HI I am xiaoming`,
adress:"艾欧尼亚"
}
class XiaoHong implements Person{
name ="小红";
age = 20;
say=()=>"哈哈哈";
adress="艾欧尼亚";
like = "玩游戏";
}
接口与接口之间的继承extends
interface Person{
name:string;
age:number;
like:any;
say:()=>void;
adress:string|string[]|(()=>string);
}
interface Teach extends Person{
job:string;
}
let wangTeach:Teach = {
name:"袁华",
age:45,
like:"秋雅",
say:()=>console.log("这道题我不会做"),
adress:"夏诺特烦恼",
job:"装男人"
}
类的定义
和
java
中一样 也有关键字:
1、private
私有的
2、protected
保护的
3、public
公开的
public的使用默认属性都会添加
interface Info{
name:string,
age:number
}
class Person{
public name:string;
age:number;//默认就是public
constructor(info:Info){
console.log(info);
this.age = info.age;
}
}
let xm = new Person({name:"a",age:18});
xm.name="小明";//可以直接修改
console.log(xm);
如果某个属性或者方法标注的是public
;那么该类被实例化后,可以直接修改属性的值,也可以被继承
private不可被子类/实例修改值不可被继承
只能在类的内部被使用
class Person{
public name:string;
private money:number;
age:number;//默认就是public
}
class Teach extends Person{
constructor(name:string){
super();
this.name = name;
this.age = 45;
this.money = 200000;//报错
}
}
let wangTeach = new Teach("王老师");
console.log(wangTeach.money);//报错
虽然
private
的属性不能被修改或者读取,但我们可以包装get和set方法
class Person{
constructor(private _age:number){}
get getAge(){//可以进行运算后返回
return this._age - 10;
}
set setAge(age:number){
this._age = age;
}
}
let p = new Person(28);
//console.log(p._age);//报错
console.log(p.getAge);//注意!!!这里不是方法调用!!!
p.setAge = 32;
console.log(p.getAge);//注意!!!这里不是方法调用!!!
注意get、set调用时不在是方法而是属性的方式
protected
可以被继承且能被子级修改值,但不能被外部(实例对象)修改值或访问值
interface Info{
name:string,
age:number
}
class Person{
public name:string;
private money:number;
protected like:string[];
age:number;//默认就是public
}
class Teach extends Person{
constructor(name:string){
super();
this.name = name;
this.age = 45;
this.like = ["教书","看书"]
}
}
let wangTeach = new Teach("王老师");
console.log(wangTeach.like);//报错
类的属性简化定义
class Person{
constructor(public name:string){}//简化定义他和下面的语句等价
/* name:string;
constructor(name:string){this.name=name;}*/
}
如果当前类被继承那么该怎么写呢?而且子级也有一个传参构造函数
class Person{
constructor(public name:string){}
}
class Teacher extends Person{
constructor(public age:number){
super("王老师");//重点
}
}
let t = new Teacher(18);
console.log(t)
注意当父类不管有没有构造函数constructor
只要子级需要使用构造函数,那么super
必须被调用
什么是
super
:可以理解成调用父类的构造函数方法
类的静态属性
在属性或者方法前加上
static
修饰词,然后使用类名.属性/方法名调用,类似json
class Persons{
static userName:string = "小明";
static sayHello(){
console.log("你好啊"+this.userName);
}
}
console.log();
Persons.sayHello();
console.log(Persons.userName);
类的只读属性
修饰词:
readonly
class Person{
constructor(public readonly name:string){}
}
let p = new Person("小明");
console.log(p.name);
p.name = "测试"//报错
console.log(p.name);
抽象类的使用
修饰词
abstract
定义的类如果被继承那么子类必须实现被abstract
修饰的属性/方法
abstract class Person{
abstract say();
abstract name:string;
}
class Student extends Person{
name = "小明";
say() {
console.log("我是学生");
}
}
class Teacher extends Person{
name = "王老师";
say(){
console.log("我是"+this.name);
}
}
tsconfig.json文件
我们知道ts文件最终会被编译成浏览器认识的js文件,那么编译过程我们可以根据我们的配置进行编译,如压缩、去掉注释等配置。
具体我们可以去查看typescript中文网,或者你可以查看相关博客
tsc -init //生成tsconfig.json文件
"include":[],只编译定义的文件
"exclude":[],排除定义的文件进行编译
"files":[]和include意义类似
"compilerOptions.removeComments":是否删除注释
"compilerOptions.strict":js严格模式
"compilerOptions.outDir":编译后输出到哪里一半我们设置为./build
"compilerOptions.rootDir":编译的跟文件路劲
"compilerOptions.sourceMap":生成map文件,改文件的用途是方便在编译后如果调试能够快速找到源文件(ts)出错的位置
"compilerOptions.noUnusedLocals":定义的变量没有被调用则不会被编译,非常有用
"compilerOptions.noUnusedParameters":定义的方法没有被调用则不会被编译,非常有用
、、、、
联合类型和类型保护
什么是联合类型?当一个变量或者形参的类型是多个的时候,使用
|
作为多类型注解;
例如const anys:()=>string|string|number|boolean;
什么是类型保护?当一个形参或者变量是联合类型时,我们不确定具体是那种类型,那我们就使用类型保护
interface Student{
study:boolean;
says:Function;
}
interface Teacher{
name:string;
lecture : ()=>{};
}
function run(animal:Student | Teacher){
//animal.lecture();//会直接报错,因为animal的类型是两种不确定实参到底是哪个所以我们要类型保护
if(animal.name){
(animal as Teacher).lecture();//类型保护
}else{
(animal as Student).says();
}
}
function run2(animal:Student | Teacher){
//animal.lecture();//会直接报错,因为animal的类型是两种不确定实参到底是哪个所以我们要类型保护
if("name" in animal){//使用in
(animal as Teacher).lecture();
}else{
(animal as Student).says();
}
}
枚举Enum
枚举值有一个固定的下标将其定义,当然我们也可以改变每个对应的下标
enum Status {
MESSAGE,TEST,MORE,OTHER
}
console.log(Status.MESSAGE);//0
console.log(Status.TEST);//1
console.log(Status.MORE);//2
console.log(Status.OTHER);//3
我们也可以只给其实枚举值一个下标,那么其他的对应下标会依次递加
enum Status {
MESSAGE=5,TEST=9,MORE=10,OTHER=23
}
console.log(Status.MESSAGE);
console.log(Status.TEST);
console.log(Status.MORE);
console.log(Status.OTHER);
我们也可以根据下标反推对应的枚举是什么
enum Status {
MESSAGE,TEST,MORE,OTHER
}
console.log(Status[0]);
console.log(Status[1]);
console.log(Status[2]);
console.log(Status[3]);
console.log(Status[4]);//undefined
泛型
软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
function testq<T>(argus:T):T{
return argus;
}
//多个泛型
function testqa<T,P>(a:T,b:P){
return `${a}${b}`;
}
function test<T>(argus:T[]){
return argus.join(",")
}
console.log(test<String>(["wq","xaa","csq"]));
类中使用泛型
class Test<T>{
constructor(private names:T[]){}
getName(index:number):T{
return this.names[index];
}
}
泛型的继承
interface Grils{
name:string;
}
class SomeSrils<T extends Grils>{
constructor(private names:T[]){}
getName(index:number):string{
return this.names[index].name;
}
}
let s = new SomeSrils([{name:"小花"},{name:"小名"},{name:"小兰"}]);
console.log(s.getName(2));
泛型的类型约束
可以理解为泛型只能为所提供的的类型中一个
//该泛型必须是number/string
class SomeSrilsy<T extends number | string>{
constructor(private names:T[]){}
getName(index:number):T{
return this.names[index];
}
}
let sy = new SomeSrilsy([1,"1","s"]);
console.log(sy.getName(2));
命名空间
命名空间可以有效防止全局变量被污染
namespace
和``
namespace Module{
class Person{
constructor(public name:string){}
}
class Teach{
constructor(public name:string){}
}
export class Student{
constructor(public name:string){}
}
export class Main{
constructor(){
new Person("小明");
new Teach("小话");
new Student("小兰");
}
}
}
new Module.Main();
使用tsconfig文件防止多个模块都被编译成多个文件。
什么意思呢?比如一个项目中有三个模块,但是我们跟文件
index.html
,只引入了打包后的main.js
。但是我们的main.js
又引入了其他三个模块组成的
//tsconfig.js
"compilerOptions.outFile":"./build/main.js"打包后的唯一文件名
"compilerOptions.module":"amd"
ts与jq一起使用
我们知道
jq
的$
会在ts中报错
解决方法一
直接声明$
declare var $:any;
解决方法二
下载@types/jquery
npm i @types/jquery -D
解决方法三
创建.d.ts
文件对jq进行声明