环境搭建
- npm i typescript -g
- 只需要安装一次
- tsc --init
- 会生成tsconfig.json文件
- 打开配置文件中"outDir": "./",的注释
- 根据需求修改
- tsc 文件名
- 执行编译生成js文件
VS半自动化环境
- 终端
- 运行任务
- tsc 监视...
- 即可实现变化保存自动编译为js文件
编译指令
tsc 1.ts --outDir ./dist
如果不加--outDir则默认会编译到ts同一目录
ts-node
tsconfig:
"./src":只编译src目录下文件,内部子文件夹不编译
"./src//":代表递归编译文件夹内部所有子文件夹 后面代表所有文件
ts-node:其他类型编译器 ts-node直接编译
ts数据类型
数字,字符串,布尔值
null underfined
数组 元组 枚举
void any Never
类型系统
string number boolean 基本类型
String Number Boolean 对象类型
基本类型可以赋值给包装类型,但是反之不可
数组(必须存储同一类型)
//基本语法
//数组的声明,此时push是没用的,因为未定义
// let arr:number[];
//数组的定义
let arr:number[]=[];
//泛型方式
// let arr1:Array;
arr.push(...[1,2,4])
console.log(arr)
元组(类型不必相同)
let data:[number,string,boolean];
data=[1,"a",true];
说明:元组再3.1之后,不能越界使用联合类型了,而且赋值和定义的类型要一一对应
联合类型
//多个类型中的一个 或的关系
let a:string|number|boolean=20;
a="a";
console.log(a)
交叉类型
//多个类型的叠加,并且的关系
let b:string&number=
枚举
enum Color{
RED,
YELLOW
}
console.log(Color.RED);//0
console.log(Color.YELLOW);//1
enum Color{
RED=1,
YELLOW
}
console.log(Color.RED);//1
console.log(Color.YELLOW);//2
Never
说明:代表那些永远不存在的值的类型,ts也可以自动推断,never此时也可以省略
function err():never{
throw new Error("error")
}
Any
说明:任意类型,在不确定数据类型的情况下使用
let a:any="a";
a=10;
console.log(a)
类型综合
/**
* 数据类型:
* 布尔类型(boolean)
* 数字类型(number)
* 字符串类型(string)
* 元组类型(tuple)
* 枚举类型(enum)
* 任意类型(any)
* null和underfined
* void类型
* never类型:从不会出现的值
*/
//不赋值,也不会有默认值
let flag:boolean=true
//第一种定义数组的方式
let arrs:number[]=[1,2,3]
//第二种定义数组的方式
let arrs1:Array=[1,2,3]
//第三种定义数组的方式
let arrs4:Array=[1,'3',true] //不会报错
//元组类型:属于数组的一种,此时数据类型和后面赋值要一一对应
let arrs2:[number,string]=[123,'this is ts']
//枚举类型
enum WEEK{
success=1, //指定枚举从1开始,不指定则默认0起始
error,//此时不指定,则为2,如果多个值中间指定赋值,则后面的依此加一
'underfined'=-1,
'null'=-2
}
let w:WEEK=WEEK.error;
console.log(WEEK.underfined); //-1
//任意类型
let num:any=123;
num='asdas';
//任意类型使用场景
let item:any=document.getElementById('test');
//null和underfined是其他(never)数据类型的子类型
let num1:undefined;
// console.log(num1) //不报错,如果定义为number类型则报错了
let num2:undefined|number;
// console.log(num2);//兼具两者的优势
function run():void{
}
//声明never的变量只能被never类型赋值:代表从不会出现的值
let a:never;
a=(()=>{
throw new Error('错误')
})()
函数
//函数表达式
let f:()=>string=function():string{
return "a"
}
let f:()=>void=function(){
}
//函数声明
function fn(x:number,y:number):number{
return x+y
}
可选参数和参数默认值
说明:通过?来定义可选参数
function fn(x:Type,y?:Type):Type
可选参数默认为undefined
可选参数必须在必传参数之后
//可选参数
function fn(x:number,y?:number):number{
return x+y
}
//参数默认值,其实因为类型推导,可直接写成y=1
function fn1(x:number,y:number=1):number{
return x+y
}
console.log(fn(1));//NaN
console.log(fn1(1));//2
补充:可选参数和默认值不要用在一个参数上
剩余参数
//剩余参数
function fn2(...arg:any[]){
console.log(arg) //[ 1, 2, 3 ]
}
fn2(1,2,3)
函数重载
//定义函数的重载格式
function fn(x:number,y:string);
function fn(x:number,y:number);
//定义函数具体的实现
function fn(x:any,y:any){
console.log(x+y)
}
fn(1,2) //3
fn(1,"2")//12
函数综合
//函数声明
function run1():void{
}
//匿名函数
let run2=function():number{
return 123;
}
let fun3=function(name:string,age:number):string{
return name+'---'+age;
}
// console.log(fun3('zq',12));
//可选参数:必须是再最后面,不能再前面
function fun4(name:string,age?:number):void{}
fun4('zq')
//默认参数:可选参数可以再默认参数之前
function fun5(name?:string,age:number=20):void{}
fun5('zq')
//剩余参数
function fun6(...res:number[]):number{
let sum=0;
for (let index = 0; index < res.length; index++) {
sum+=res[index];
}
return sum;
}
// console.log(fun6(1,2,3,4,5,6));//21
//剩余参数形式二
function fun7(a:number,...res:number[]):number{
let sum=0;
for (let index = 0; index < res.length; index++) {
sum+=res[index];
}
return sum;
}
// console.log(fun7(1,2,3,4,5,6));//20
//es5中出现同名函数,下面会替换上面的,即使参数不同
//函数重载:必须要有一个any的实现
function func(name:string):string;
function func(age:number):number;
function func(str:any):any{
if(typeof str==='string'){
return '我叫: '+str
}else{
return '我的年龄是: '+str
}
}
// console.log(func('a'),func(12));我叫: a 我的年龄是: 12
ts中的this
案例一:
let obj={
a:10,
fn(){
//函数中默认this指向是any,通过下面再配置文件中解决,而且如果是类似于
//document中事件的this,ts会自动推导出类型,this指向不需要下面配置也是事件对象
// "noImplicitThis": true
console.log(this.a)
//注意:此时this的指向只是是否有提示的问题,真的执行代码配置文件设置不设置值都是10
}
}
案例二:
let obj={
a:20,
fn(this:Document){
console.log(this.querySelector)
}
}
document.onclick=obj.fn
说明:如果配置配置了this指向"noImplicitThis": true,
则fn中的this指向就是obj对象,但是此时obj.fn指向了
点击事件,为了有提示信息,需要手动指定this指向this:Document
这个this参数其实是一个假参数,ts编译时候会被去掉,纯粹是
为了代码提示而存在,this指向配置不配置,修改不修改都不影响最后的结果
修饰符
public protected(该类和子类能访问) private(类,对象内部) readonly(类,对象内部可用,其他只读)
案例一:
class Person{
readonly n:number;
constructor(num:number){
this.n=num;
}
}
let p=new Person(20)
案例二:简写方式
class Person{
constructor(public num:number){
this.n=num;
}
}
let p=new Person(20)
说明:因为ts不同于js,构造函数中属性需要先声明才能
使用,此时public num:number此种方式就是相当于提前再
class中先声明了一份
存取器
class Person{
//私有属性,大家默认的规则是下划线
private _num:number;
//存取器
//存取器再ts中不是当做方法使用的,而是被当做属性
get num():number{
return this._num
}
set num(num:number){
if(num>0){
this._num=num;
}
}
}
let p=new Person()
p.num=-10
console.log(p.num);//undefined
静态
class Person {
private static instance;
private constructor() { }
public static getInstance() {
if (!Person.instance) {
Person.instance = new Person();
}
return Person.instance;
}
}
let p=Person.getInstance();//相等
let p1=Person.getInstance();
抽象类
abstract class Person {
constructor() { }
abstract study():void;
}
class Student extends Person{
study(): void {
console.log("学习");
}
}
let s=new Student();
s.study()
类综合
class Person{
private name:string;
constructor(n:string){
this.name=n;
}
run():void{
console.log(this.name);
}
}
/**
* 类中属性修饰符
* public: 公有 都可以访问,默认值
* protected: 保护类型 在该类和子类能访问
* private: 在该类能访问
*/
class Student extends Person{
//实际上在新版本ts,构造函数在此时可省略,方法执行依然正常
// constructor(n:string){
// super(n)
// }
static sex='男';//静态属性
//静态方法里面只能使用静态属性
static print(){
console.log("静态方法",this.sex);
}
//重写父类方法
run():void{
console.log("重写方法");
}
}
let s=new Student('zq');
// s.run() //zq ,不重写父类方法的情况下
// Student.print()
// s.run() //输出: 重写方法 重写父类的方法
/**
* 抽象类:
* 提供其他类继承的基类,不能实例化
* abstract关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类中实现
*
* 抽象类中可以有属性,构造函数,和非抽象方法
*/
abstract class Animal {
name:string;
constructor(name:string){
this.name=name;
}
abstract eat():any;
run(){
console.log(this.name+" 跑步");
}
}
class Dog extends Animal{
eat() {
console.log("狗吃饭");
}
}
let d=new Dog('pf');
d.eat();
d.run();//pf 跑步
接口
//接口不能有任何属性和方法实现,只能有抽象描述
interface Options{
num:number;
//可选的
name?:string;
say();
}
class optImpl implements Options{
num: number;
constructor(num:number){
this.num=num;
}
say() {
console.log(this.num+"说话");
}
}
function fn(opts:Options) {
opts.say()
}
fn(new optImpl(20))
断言
interface Options{
num:number;
name:string;
}
function fn(opts:Options) {
}
//断言
//按理说必须传入{num:20,name:"呵呵"}类似的才能通过
//但是通过断言可强制判定传入参数是什么类型
fn({} as Options)
补充:
let obj={
num:10,
name:"saa",
a:1
}
fn(obj)
说明:如果把传入的参数先赋值好在传入,可以避免规则检测
不会报错,但是此种情况只能在传入的obj覆盖全部所需参数
情况下,也就是说只能多不能少
索引签名
/**
* 索引签名:
* 希望规则是:一组由数字进行key命名的对象
* 补充:索引签名的key类型只能是string或者number
* 索引签名在下面传参时候,是可有可没有的,而且不限制个数
*/
interface Options{
//key是number,value是any类型的数据
[atrr:number]:any;
}
function fn(opts:Options) {
}
fn({
0:1,
2:20
})
函数类型接口
/**
* 函数类型接口
* 是一个包含由fn并且值的类型为函数的结构体
* 并不是描述函数结构而是一个包含函数的对象结构
*/
interface Options{
fn:Function
}
let o:Options={
fn:function(){
}
}
/**
* 下面约定就是函数结构,而不是包含有函数的对象结构了
*/
interface IFn{
(x:number):number
}
let fn:IFn=function(x:number){return x}
/**
* 下面是函数结构的实践
* 因为interface的约定,保证了传参的正确性
* 在编译阶段避免了出错
*/
interface MouseEventCallBack{
(e:MouseEvent):any;
}
let fn1:MouseEventCallBack=function(e:MouseEvent){}
document.onclick=fn1;
补充案例
interface AjaxData{
code:number;
data:any;
}
interface AjaxCallBack{
(rs:AjaxData):any
}
function ajax(callback:AjaxCallBack){
callback({
code:200,
data:{}
})
}
泛型
/**
* 泛型:
* 很多时候,类型写死,不利于复用
*/
//泛型变量
function fn(args:T):T{
return args;
}
function fn1(args:T,args1:S):[T,S]{
return [args,args1];
}
//数组形式
function fn2(args:T[]):T[]{
return args;
}
function fn3(args:Array){}
泛型类
class MyArray{
private _data:T[]=[];
public push(v:T):number{
return this._data.length;
}
}
let a=new MyArray();
a.push("a")
let b=new MyArray();
b.push(1)
泛型类型
//泛型类型
let fn:(x:T,y:T)=>number=function(x,y){
return Number(x)+Number(y)
}
let fn1=function(x:T,y:S):number{
return Number(x)+Number(y)
}
console.log(fn(1,2));//3
console.log(fn1(1,"2"));//3
泛型接口
interface IFN{
(x:T,y:S):S
}
let fn2:IFN=function(x,y){
return Number(x)+Number(y);
}
类类型
/**
* 类类型:
* 表示这个类型对应的对象
*/
//错误实例:此时Array代表就是类类型,但是需要的参数是该类
//对应的构造函数
function getArray1(constructor:Array){
return new constructor();
}
getArray2(Array)
//补充:p后面的Person就是类类型
let p:Person=new Person();
//下面是构造函数
let fn1:{new ():Person}
//正确写法
function getArray2(constructor:{new():Array}){
return new constructor();
}
getArray2(Array)
泛型约束
形式一:
function fn(a:T){
console.log(a);
}
形式二:
interface Len{
length:number
}
function fn1(a:T){
console.log(a.length);
}
fn1("a")//此时在fn1(1)则会报错,因为数字类型没有length属性
接口综合
//ts自定义方法传入参数 对json进行约束
function printLabel(labelInfo:{label:string}):void{
console.log("printlabel");
}
// printLabel('name')//错误
// printLabel({name:'haha'})//错误
printLabel({label:'haha'}) //正确
//一、函数类型接口
/**
* 以上只是针对单一方法进行约束,那么批量约束呢
* 1. 属性接口 对json的约束
*/
interface FullName{
firstName:string;
secondName:string;
}
function printName(name:FullName){
console.log(name.firstName,name.secondName);
}
let obj={
age:20,
firstName:'zq',
secondName:'pf',
}
//注意: 传入对象引用,只要包含必须项即可,但是如果直接传入对象,则不能包含多余属性
//但是建议,不要添加多余属性
printName(obj)
/**
* 2. 可选属性
*/
interface FullName1{
firstName:string;
secondName?:string; //可选属性
}
function printName1(name:FullName1){
console.log(name.firstName,name.secondName);
}
printName1({firstName:'zq'})//zq undefined
/**
* 3.函数类型接口
* 对方法的传入参数和返回值进行约束
*/
interface encrypt{
(key:string,value:string):string;
}
let md5:encrypt=function(key:string,value:string):string{
return 'haha';
}
//1. 可索引接口:数组,对象的约束(不常用)
interface UserArr{
[index:number]:string;
}
let arr:UserArr=['aaa','bbb']
interface UserObj{
[index:string]:string;
}
let obj1:UserObj={name:'20'}
//2. 类类型接口: 对类的约束和抽象类有点相似
interface Animal1{
name:string;
eat1(str:string):void;
}
class Dog1 implements Animal1{
name: string;
constructor(name:string){
this.name=name;
}
eat1(str: string): void {
console.log(this.name,str);
}
}
let d1=new Dog1('zq');
d1.eat1('haha'); //zq haha
//1. 接口扩展:接口继承其他接口
interface A1{
eat():void;
}
interface A2{
fly():void;
}
//接口可以多继承
interface P1 extends A1,A2{
run():void
}
//类只能单继承,但是可以多实现,而且继承和实现可并存
class P2 implements P1{
run(): void {
}
eat(): void {
}
fly(): void {
}
}
泛型综合
// 泛型基本使用
function getData(name:T):T{
return name;
}
console.log(getData('zq'));
console.log(getData(12));
//泛型类
class MinClass{
public list:T[]=[]
add(value:T):void{
this.list.push(value)
}
min():T{
let min=this.list[0];
return min;
}
}
let m=new MinClass();
// 泛型接口
//1. 方式一
interface ConfinFn{
(v1:T):T;
}
let fn:ConfinFn=function(v1:T):T{
return v1;
}
fn('haha')
//2. 方式二
interface ConfinFn1{
(v1:T):T;
}
function fn2(v1:T):T{
return v1;
}
let fn3:ConfinFn1= fn2;
fn3('sad')
/**
*泛类 把类当作参数的泛型类
*/
class User{
//之所以加underfined是因为不初始化报错,再多加一个类型就不报错了
username:string|undefined;
password:string|undefined;
}
//这样就可以把各种外部类传递进来
class MysqlDB{
add(user:T):boolean{
return true;
}
}
let m1=new MysqlDB();
// m1.add(new User())
命名空间和模块
export.ts
export namespace B {
export let url = 'safas';
export function getData(): any[] {
return [
1, 2, 3
]
}
// export {url,getData} 一次统一暴露,但是使用此方式,上面的export就不能存在了
// export default getData 一个模块只能用一次,引入方式也有区别
}
export namespace C{
export let url1 = 'safas';
export function getData1(): any[] {
return [
1, 2, 3
]
}
}
modules.ts
/**
* 命令空间: 内部模块,主要用于组指代码,避免命名冲突(其实针对的就是一个文件同名方法的冲突解决方案)
* 模 块 : ts的外部模块的简称,侧重代码复用,一个模块里可能有多个命令空间
*/
import { B,C } from './export';
// import getData from './export'; export default方式导出的时候的引入方式
//如果导出时候没有命名空间 则可以通过as取别名
import { getData as get } from './export';
B.getData();
装饰器
- 方式一:配置文件(开启装饰器):"experimentalDecorators": true
- 方式二:根据vscode错误提示(装饰器错误提示)也可以自动修改
/**
* 装饰器:
* 类装饰器 属性装饰器 方法装饰器 参数装饰器
*
* 装饰器写法:
* 普通装饰器(无法传参)
* 装饰器工厂(可传参)
*
* 通俗的讲:装饰器就是一个方法
*/
//1. 类装饰器
namespace A{
function logclass(params:any){
//params其实就是被装饰的类
// console.log(params);
//扩展属性
params.prototype.apiURL='www.zq.com';
params.prototype.run=function(){
console.log('run');
}
}
@logclass
class HttpClient{
getData(){
}
}
let h:any=new HttpClient();
// console.log(h.apiURL); //www.zq.com
// h.run()
}
//2. 类装饰器(带参数)
namespace B{
function logclass(params:string){
//此时params就是hello
//target就是被装饰的类
return function(target:any){
// console.log(params,target);
}
}
//装饰的过程是直接执行的,不需要实例化,不需要调用
@logclass('hello')
class HttpClient{
getData(){
}
}
let h=new HttpClient();
}
/**
* 重载构造的小例子
* 类的装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数
* 如果类的装饰器返回一个值,它会使用提供的构造函数来替换类的声明
*/
namespace C{
function logclass(target:any){
return class extends target{
apiUrl='修改路径';
getData(){
console.log('修改前');
this.apiUrl='修改路径'+'-----';
console.log(this.apiUrl);
}
}
}
@logclass
class HttpClient{
apiUrl:string|undefined;
constructor(){
this.apiUrl='路径初始化'
}
getData(){
console.log(this.apiUrl);
}
}
let h=new HttpClient();
// h.getData() //修改前 修改路径-----
}
/**
* 属性装饰器:
* 属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:
* 1. 对于静态成员来说是类的构造函数,对于实例成员时类的原型对象
* 2. 成员的名字
*/
namespace D{
function logurl(params:string){
return function(target:any,attr:any){
// console.log(target,attr);
//虽然这样可修改,但是通过打印发现target[attr]是undefined,很奇怪
//而且必须这个属性如果在构造函数有赋值,构造函数内部赋值会替换装饰器的赋值,因为构造是后执行的
target[attr]=params;//相当于用@logurl('xxx')的参数给apiUrl重新赋值
}
}
class HttpClient{
@logurl('xxx')
apiUrl:string|undefined;
getData(){
console.log(this.apiUrl);
}
}
let h=new HttpClient();
// h.getData()
}
/**
* 方法装饰器:
* 会被应用到方法的属性描述符上,可以用来监视,修改或者替换方法定义
* 方法装饰器参数:
* 1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象:{getData: ƒ, constructor: ƒ}
* 2. 成员的名字(方法名称): "getData"
* 3. 成员的属性描述符: {value: ƒ, writable: true, enumerable: true, configurable: true}
*/
//方式装饰器一
namespace D{
function logMethods(params:any){
return function(target:any,methodsName:any,desc:any){
// console.log(target,methodsName,desc);
//扩展属性和方法
target.apiUrl=params;
target.run=function(){
console.log(this.apiUrl);
}
}
}
class HttpClient{
@logMethods('www')
getData(){
console.log('执行');
}
}
let h:any=new HttpClient();
// h.getData();//执行
// h.run();//www
}
//方法装饰器二
namespace E{
function logMethods(params:any){
return function(target:any,methodsName:any,desc:any){
/**
* ƒ () {
console.log('执行');
}
*/
// console.log(desc.value);
//修改装饰器的方法,把装饰器方法里面传入的参数修改为string类型
//1. 保存当前方法
let om=desc.value;
desc.value=function(...args:any[]){
console.log("执行原始方法前");
args=args.map((value)=>{
return String(value)
})
//如果需要绑定上下文,甚至传参到原始方法
om.apply(this,args);
console.log("执行原始方法后");
}
//如果不涉及上下文调用原始的getData方法,则可以把方法调用放在外面
// om();
}
}
class HttpClient{
@logMethods('www')
getData(...args:any[]){
args.forEach(element => {
console.log(element);
});
}
}
let h:any=new HttpClient();
// h.getData(1,2,3,4);
/**
* 输出结果:
* 执行原始方法前
1
2
3
4
执行原始方法后
*/
}
/**
* 方法参数装饰器:用处不大,可能有复杂用法,但是基本可以通过类装饰器替代,不必研究
* 参数装饰器表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元素数据,
* 参数列表:
* 1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象:{getData: ƒ, constructor: ƒ}
* 2. 方法的名字: "getData"
* 3. 参数在函数参数列表中索引 : 0
*/
//? 代表可选参数:可传可不传
function logParams(params?:any){
return function(target:any,methodsName:any,index:any){
// console.log(target,methodsName,index);
target.apiURL=params;//扩展属性
}
}
class HttpClient{
getData(@logParams('xxx') uuid:any){
console.log(uuid);
}
}
let h:any=new HttpClient();
h.getData();
/**
* 装饰器执行顺序:
* 属性装饰器-》方法装饰器-》方法参数装饰器-》类装饰器
* 同类装饰器存在多个:从后向前,从内到外的顺序
*/