环境搭建
- npm i typescript -g
- 只需要安装一次
- tsc --init
- 会生成tsconfig.json文件
- 打开配置文件中"outDir": "./",的注释
- 根据需求修改
- tsc 文件名
- 执行编译生成js文件
- tsc --version 获取版本号
VS半自动化环境
- 终端
- 运行任务
- tsc 监视...
- 即可实现变化保存自动编译为js文件
也可以配置npm脚本实现
"build": "tsc",
"build:watch": "tsc --watch"
编译指令
tsc 1.ts --outDir ./dist
如果不加--outDir则默认会编译到ts同一目录
ts-node
各种规范(和ts无关)
- AMD CMD require.js sea.js 都基本上过时,不需要关心
- node commonjs commonjs2
- es6 module
- umd 兼容以上三种
tsconfig
"./src":只编译src目录下文件,内部子文件夹不编译
"./src//":代表递归编译文件夹内部所有子文件夹 后面代表所有文件
ts-node:其他类型编译器 ts-node直接编译
直接运行
- vscode按照coderunner插件
- npm install ts-node -g
- 运行即可
ts数据类型
数字,字符串,布尔值
null underfined
它们俩是其他类型的子类型,可以赋值给其他类型
例如:let name:string=null;
但是需要打开:"strictNullChecks": false, 否则报错
不然就只能let name:string|null=null;
数组 元组 枚举
void any Never
类型系统
string number boolean 基本类型
String Number Boolean 对象类型
基本类型可以赋值给包装类型,但是反之不可
数组(必须存储同一类型)
//基本语法
//数组的声明,此时push是没用的,因为未定义
// let arr:number[];
//数组的定义
let arr:number[]=[];
//泛型方式
// let arr1:Array<number>;
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)
枚举
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
//可以修改某个值然后后续的值顺延即可
enum Week{
MONDAY=2,
TUESDAY
}
console.log(Week.TUESDAY);//3
- 常数枚举
常数枚举后续不可修改,所以在编译成js时候直接输出部分是0,1数字而不是变量
const enum Colors{
Red,
Yellow
}
console.log(Colors.Red,Colors.Yellow); //0 1
- 枚举的兼容性
枚举类型与数字类型兼容,并且数字类型与枚举类型兼容;不同枚举类型之间是不兼容的
Never
其他类型的子类型
说明:代表那些永远不存在的值的类型,ts也可以自动推断,never此时也可以省略
返回值是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<number>=[1,2,3]
//第三种定义数组的方式
let arrs4:Array<any>=[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
}
type
type用来定义类型或者类型别名
type GetUserName = (firstName: string, lastName: string) => {name:string};
let getUserName: GetUserName = function (firstName: string, lastName: string): {name:string} {
return {name:firstName+lastName};
}
- 泛型类型别名(扩展)
type Cart<T>={list:T[]}|T[]; //联合类型
let c1:Cart<string>={list:['1']};
let c2:Cart<string>=['1']
可选参数和参数默认值
说明:通过?来定义可选参数
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 sum(...numbers:Array<number>) {
//accu是最终要返回的值
return numbers.reduce((accu,item)=>accu+item,0);
}
函数重载
//注意:这三行除了注释之外,必须紧紧的贴在一起,否则报错
//定义函数的重载格式
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
- 函数参数的协变
不推荐这么使用,了解即可
type logFunc=(a:number|string)=>void;
let log:logFunc;
function log1(a:number|string|boolean) {
}
log=log1;//正确
//其实很好理解,ts不认类型,只要包含即可,明显log1更多选择可以赋值给更小选择的
函数综合
//函数声明
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 Animal {
speak(word:string){
console.log('a');
}
}
class Cat {
//重写
speak(word:string){
console.log('b');
}
}
重载其实就是函数重载
类综合
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 Point{
x:number;
y:number;
}
let ponit:Point={x:0,y:0};
- 描述行为的抽象
//接口不能有任何属性和方法实现,只能有抽象描述
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))
- 类可以实现多个接口,但是只能继承一个父类
接口补充
- 接口的readonly
interface Circle{
readonly PI:number;
}
let circle:Circle={
PI:3.14
}
// circle.PI=0; 无法分配到 "PI" ,因为它是只读属性
- 索引签名(任意属性)
/**
* 索引签名:
* 希望规则是:一组由数字进行key命名的对象
* 补充:索引签名的key类型只能是string或者number
* 索引签名在下面传参时候,是可有可没有的,而且不限制个数
*/
interface Options{
//key是number,value是any类型的数据
[atrr:number]:any;
}
function fn(opts:Options) {
}
fn({
0:1,
2:20
})
namespace a{
interface PlainObject{
[propNmae:string]:number;//key value形式;这样可以任意多个值
}
let obj:PlainObject={
x:1,
y:2,
z:3
}
//这数组进行约束,因为数组的key其实就是索引,所以本质还是key-value形式
interface UserInterface{
[index:number]:string;
}
let arr:UserInterface=['1','2','3']
}
- 接口约束构造函数
使用new来约束构造函数
interface WithNameClass{
new(name:string):Animal;
}
class Animal{
//public name:string 就相当于 this.name=name了
constructor(public name:string){}
}
//使用
function createAnimal(clazz:WithNameClass,name:string) {
return new clazz(name);
}
createAnimal(Animal,'zq');
断言
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覆盖全部所需参数
情况下,也就是说只能多不能少
接口的兼容性
ts跟类型没有关系,只和有没有有关系;简单说:我包含你的,我就可以给你这种类型传参
interface Animal{
name:string;
}
interface Person{
name:string;
speak:(words:string)=>void;
}
function getNmae(animal:Animal) {
console.log(animal.name);
}
let p:Person={
name:'zq',
speak(){
}
}
getNmae(p);
//此处传p也正确,因为Animal有的Person都有,所以也符合规则
函数类型接口
/**
* 函数类型接口
* 是一个包含由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:{}
})
}
泛型
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而是在使用的时候再指定类型的一种特性;泛型T作用域只限于函数内部使用
/**
* 泛型:
* 很多时候,类型写死,不利于复用
*/
//泛型变量
function fn<T>(args:T):T{
return args;
}
function fn1<T,S>(args:T,args1:S):[T,S]{
return [args,args1];
}
//数组形式
function fn2<T>(args:T[]):T[]{
return args;
}
function fn3<T>(args:Array<T>){}
泛型类
class MyArray<T>{
private _data:T[]=[];
public push(v:T):number{
return this._data.length;
}
}
let a=new MyArray<string>();
a.push("a")
let b=new MyArray<number>();
b.push(1)
泛型类型
//泛型类型
let fn:<T>(x:T,y:T)=>number=function(x,y){
return Number(x)+Number(y)
}
let fn1=function<T,S>(x:T,y:S):number{
return Number(x)+Number(y)
}
console.log(fn(1,2));//3
console.log(fn1<number,string>(1,"2"));//3
泛型接口
interface IFN<T,S>{
(x:T,y:S):S
}
let fn2:IFN<string,number>=function(x,y){
return Number(x)+Number(y);
}
接口泛型
很奇怪的用法,不太常用
interface Calculate{
<T>(a:T,b:T):T
}
let add:Calculate=function<T>(a:T,b:T):T{
<!--报错,因为传入类型不一定可以相加-->
<!--return a+b; -->
return a;
}
add<number>(1,2);
- (泛型可以有多个)面试题:不增加中间变量的情况下,交换两个变量的值
function swap<A,B>(tuple:[A,B]):[B,A] {
return [tuple[1],tuple[0]];
}
swap<string,number>(['zq',10]);
默认泛型类型
function swap<A=number,B=string>(tuple:[A,B]):[B,A] {
return [tuple[1],tuple[0]];
}
swap([1,'zq']);
// swap<string,number>(['zq',1]);
类类型
/**
* 类类型:
* 表示这个类型对应的对象
*/
//错误实例:此时Array代表就是类类型,但是需要的参数是该类
//对应的构造函数
function getArray1(constructor:Array<number>){
return new constructor();
}
getArray2(Array)
//补充:p后面的Person就是类类型
let p:Person=new Person();
//下面是构造函数
let fn1:{new ():Person}
//正确写法
function getArray2(constructor:{new():Array<string>}){
return new constructor();
}
getArray2(Array)
泛型约束
形式一:
function fn<T extends number>(a:T){
console.log(a);
}
形式二:
interface Len{
length:number
}
function fn1<T extends Len>(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<T>(name:T):T{
return name;
}
console.log(getData('zq'));
console.log(getData(12));
//泛型类
class MinClass<T>{
public list:T[]=[]
add(value:T):void{
this.list.push(value)
}
min():T{
let min=this.list[0];
return min;
}
}
let m=new MinClass<number>();
// 泛型接口
//1. 方式一
interface ConfinFn{
<T>(v1:T):T;
}
let fn:ConfinFn=function<T>(v1:T):T{
return v1;
}
fn<string>('haha')
//2. 方式二
interface ConfinFn1<T>{
(v1:T):T;
}
function fn2<T>(v1:T):T{
return v1;
}
let fn3:ConfinFn1<string>= fn2;
fn3('sad')
/**
*泛类 把类当作参数的泛型类
*/
class User{
//之所以加underfined是因为不初始化报错,再多加一个类型就不报错了
username:string|undefined;
password:string|undefined;
}
//这样就可以把各种外部类传递进来
class MysqlDB<T>{
add(user:T):boolean{
return true;
}
}
let m1=new MysqlDB<User>();
// 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. 对于静态成员来说是类的构造函数(例如 :Person),对于实例成员是类的原型对象(例如:Person.prototype)
* 2. 成员的名字
*/
namespace D{
function logurl(params:string){
return function(target:any,propertyName:string){
// console.log(target,propertyName);
//虽然这样可修改,但是通过打印发现target[propertyName]是undefined,很奇怪
//而且必须这个属性如果在构造函数有赋值,构造函数内部赋值会替换装饰器的赋值,因为构造是后执行的
target[propertyName]=params;//相当于用@logurl('xxx')的参数给apiUrl重新赋值
//注意:后面还有Object.defineProperty的形式实现的,不直接这么 target[propertyName]=params赋值;因为有的是没有参数的,需要动态监听
}
}
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};
* 重点: 另外属性描述器的value属性就是指向被装饰的方法
*/
//方式装饰器一
namespace D{
function logMethods(params:any){
return function(target:any,methodsName:string,desc:PropertyDescriptor){
// 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:PropertyDescriptor){
/**
* ƒ () {
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. 对于静态成员来说是类的构造函数(Person),对于实例成员是类的原型对象:{getData: ƒ, constructor: ƒ} 例如:Person.prototype
* 2. 方法的名字: "getData"
* 3. 参数在函数参数列表中索引 : 0
*/
//? 代表可选参数:可传可不传
function logParams(params?:any){
return function(target:any,methodsName:string,index:number){
// 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();
/*
* 1. 属性和方法装饰器,谁先写谁先执行
* 2. 方法装饰器又分为方法的和方法参数的,先参数后方法
* 3. 最后是类装饰器
* 4. 同类型的,先执行后写的,从内到外
*/
- 属性装饰器和方法装饰器
namespace a1{
function upperCase(target:any,prototyName:string) {
let value=target[prototyName];
const getter=()=>value;
const setter=(newVal:string)=>{
value=newVal.toUpperCase();
}
delete target[prototyName];//删掉之前的属性描述器
Object.defineProperty(target,prototyName,{
get:getter,
set:setter,
enumerable:true,
configurable:true
})
}
//方法的
function methodEnumerable(params:boolean) {
//propertyDescriptor:老的属性描述器
return function(target:any,prototyName:string,propertyDescriptor:PropertyDescriptor) {
propertyDescriptor.enumerable=params;
}
}
class Person {
@upperCase
name:string='zq';
@methodEnumerable(true)
public getName(){
}
}
let p=new Person();
console.log(p.name);//ZQ
for (const attr in p) {
console.log(attr);
}
}
类型保护
- 类型保护就是一些表达式,他们再编译的时候就能通过类型信息确保某个作用域内变量的类型
- 类型保护就是能通过关键字判断出分支中的类型
以下是案例:
- typeof
//最主要就是再对应判断里面有对应函数的提示
function double(input: string | number | boolean) {
if (typeof input==='string') {
input.toLowerCase();
}else if (typeof input==='number') {
input.toFixed(2);
}else{
input;
}
}
- instanceof
重点是加了判断,代码提示也有了,而且不会提示多余的属性
class Animal {
public name:string='zq';
}
class Bird extends Animal{
public swing:number=2;
}
function getName(a:Animal) {
if (a instanceof Bird) {
a.swing;
} else {
a.name;
}
}
- 链式判断运算符
//先判断a是不是null/underfined,如果是则直接返回null或underfined,否则返回a.b的值
a?.b
- 可辨识的联合类型
利用联合类型中的共有字段进行类型保护的一种技巧,相同字段的不同取值就是可辨识
interface WarningButton{
class:'warning',
text1:'1'
}
interface DangerButton{
class:'danger',
text2:'11'
}
type Button=WarningButton|DangerButton;
function getButton(btn:Button) {
//直接写,代码只提示共有属性class
//如下才行
if (btn.class==='warning') {
btn.text1;
}else{
btn.text2;
}
}
- in操作符
interface WarningButton{
text1:'1'
}
interface DangerButton{
text2:'11'
}
function getButton(btn:WarningButton|DangerButton) {
if ('text1' in btn) {
btn.text1;
} else {
btn.text2;
}
}
- 自定义的类型保护
自定义类型保护,其实就是定义了一个函数,函数的返回值是一个类型的谓词,形式是params is Type;params必须是当前函数签名里面的一个参数名
interface WarningButton{
name1:'1',
text1:'1'
}
interface DangerButton{
name2:'2',
text1:'11'
}
function isWarning(x:WarningButton|DangerButton):x is WarningButton {
//规则自定义
return x.text1==='1';
}
function getButton(btn:WarningButton|DangerButton) {
if (isWarning(btn)) {
btn.name1;
} else {
btn.name2;
}
}
类型变换
- 交叉类型
//多个类型的叠加,并且的关系
let b:string&number=
interface Bird{
name:string;
fly():void;
}
interface Person{
name:string;
eat():void;
}
//交叉类型其实就是两个接口类型的属性的并集
type BirdMan=Bird&Person;
- typeof
可以获取一个变量的类型
/*
type Person={
name:string;
age:number
}
*/
let p={
name:'zq',
age:10
}
type Person=typeof p;
let p2:Person={
name:'qq',
age:20
}
- 索引访问操作符
通过[]获取一个类型的子类型
interface Person{
name:string;
job:{
name:string
}
}
let n:Person['job']['name']='fe';
- keyof
索引类型查询操作符
interface Person {
name: string;
gender: 'male' | 'female';
//方式一:添加任意属性
// [propName:string]:any;
}
//报错:因为无法确定传入的key,可能Person里面没有这个key,所以return报错
// function getValueKey(val: Person, key: string): any {
// return val[key];
// }
//方式二
// type PKeys='name'|'gender';
type PKeys=keyof Person; //返回一个接口key的集合;这样避免修改key之后也要修改此处
function getValueKey(val: Person, key: PKeys): any {
return val[key];
}
let p: Person = {
name: 'zq',
gender: 'female'
};
getValueKey(p,'gender');
- 映射类型
再定义的时候用in操作符批量定义
interface Person {
name: string;
gender: 'male' | 'female';
}
type PartialPerson={
//? 代表可选 变成赋值的时候不需要全部都写了
[key in keyof Person]?:Person[key]
}
let p: PartialPerson = {
name: 'zq',
// gender:'female' 可以不写,因为成了可选的
};
内置工具类型
TS中内置了一些工具类型来帮助我们更好的使用类型系统,虽然下面案例都是使用接口的,但是实际上class也可以的
- Partial
将传入的属性由非可选变成可选
interface Person {
name: string;
gender: 'male' | 'female';
}
let p: Partial<Person> = {
name: 'zq'
};
实现原理
type Partial<T>={
[key in keyof T]?:T[key]
}
- Required
将传入的属性中的可选项变为必选项
interface Person {
name: string;
gender?: 'male' | 'female';
}
let p: Required<Person> = {
name: 'zq',
gender:'female'
};
实现原理
type Required<T>={
//注意这里是-?
[key in keyof T]-?:T[key]
}
- ReadOnley
属性变成只读
interface Person {
name: string;
gender?: 'male' | 'female';
}
type ReadOnlyPerson=Readonly<Person>
let p: ReadOnlyPerson = {
name: 'zq',
gender:'female'
};
// p.gender='zq';无法分配到 "gender" ,因为它是只读属性。
实现原理
type Readonly<T>={
Readonly [key in keyof T]:T[key]
}
- Pick
从传入的属性中摘取某
一项
返回
interface Person {
name: string;
gender?: 'male' | 'female';
}
type PickPerson=Pick<Person,'name'>;
let p: PickPerson = {
name: 'zq'
};
实现原理
//keyof T=name|gender 是个联合类型
type Pick<T,K extends keyof T>={
[key in K]:T[key]
}
- 映射类型修饰符的控制
TS中增加了对映射类型修饰符的控制,具体而言,一个readonly或?修饰符里可以用前缀+或-来表示这个修饰符应该被添加或移除。TS中部分内置工具类型利用了这个特性(Partial...)
条件类型
再定义泛型的时候能够添加进逻辑分支,泛型使用更灵活
案例一
interface A{
name1:string
}
interface B{
name2:string
}
interface C{
name3:string
}
interface D{
name4:string
}
//判断T是不继承A
//ts判断是否继承是根据属性,所以即使传进入是A,因为属性都有,所以也可以
type Condition<T>=T extends A?B:C;
let c:Condition<A>={name2:'zq'};
案例二
interface A{
name1:string
}
interface B{
name2:string
}
interface C{
name3:string
}
interface D{
name4:string
}
type Condition<T>=T extends A?B:C;
//其实最终效果就是 B和C的交集
let c:Condition<A|D>={name2:'zq',name3:'zq1'};
TS内置了很多条件类型
- Exclude
从T可分配的类型中排除U
type E=Exclude<string|number,string>;
let e:E=10;
- Extract
从T可分配的类型中提取U
type E=Extract<string|number,string>;
let e:E='1';
- NonNullable
从T中排除null和underfined
type E=NonNullable<string|number|null|underfined>;
let e:E='hello';
let e:E=10;
- ReturnType
获取函数类型的返回类型,并作为新的类型
function getUserInfo() {
return {name:'zq',age:10}
}
type UserInfo=ReturnType<typeof getUserInfo>
let u:UserInfo={name:'zq',age:20};
- InstanceType
获取构造函数的实例类型
class Person {
name:string;
constructor(name:string) {
this.name=name;
}
}
type P=InstanceType<typeof Person>
let p:P={name:'zq'};
let p:P=new Person('zq');
类型声明
- 声明文件可以让我们不需要将js重构为TS,只需要加上声明文件就可以使用系统
- 类型声明再编译的时候都会被删除,不会影响真正的代码
- 可以把类型声明放在一个单独的类型声明文件中
- 文件命令规范为*.d.ts
- @types是一个约定前缀,所有第三方声明的类型库都会有这样的前缀(例如@types/node @types/jquery)
- TS核心库的类型声明文件
- 一般*.d.ts文件都在typings文件夹下面
使用案例
//jquery.d.ts
declare function jQuery(selector: string): any;
declare namespace jQuery {
function ajax(url: string): void;
}
//需要导出,否则外部无法使用
export default jQuery;
声明示例
declare let age: number;
declare function getName(): string;
declare class Animal {
naem: string
}
declare interface Person {
name: string
}
//declare内部不需要加declare
declare namespace jQuery{
function ajax(url:string):void;
let name:string;
namespace fn{
function extend(object:any):void;
}
}
注意:vscode下一般都可以找到,但是我们需要是的特定目录下找到定义文件,所以可以通过tsconfig.json文件实现。
补充
- 无法重新声明块范围变量“name”
在ts中有个lib.dom.ts;类似于全局文件,里面声明的有name属性,所以这样直接写不行,需要把该ts文件转换成模块
- 代码里面有export import之类的代码,那么这个文件就变成一个模块
- ts为dom提供了一整套类型声明
let root: HTMLElement | null = document.getElementById('root');
//!强行断言 root不是null,这样就不会报错了,否则root可能为null,说白了欺骗编译器
root!.style.color = 'red';
- 包装对象 (java中的装箱拆箱)
let name1: string = 'zf';
// name1.toLowerCase();
let name11 = new String(name1);
name11.toLowerCase();
let isOk1: boolean = true;
let isOk2: boolean = Boolean(1); //拆箱为boolean
// let isOk3:boolean=new Boolean(1); 报错 对象赋值给了基本类型
自动在基本类型和对象类型之间切换;基本类型上没有方法;在内部迅速的完成一个装箱的操作,把基本类型迅速包装成对象类型,然后用对象来调用方法
- 类型断言
let name2: string | number;
此时直接调用name2只能调用string和number的共同方法;要么调用前赋值,然后回自动推导;要么就使用类型断言,就可以正常调用方法了;(name2 as string).toLowerCase();
- 字面量类型
后面赋值只能是'boy'或'girl'
; let G1:'boy'|'girl';
- 使用注意
关键字 | 作为类型使用 | 作为值使用 |
---|---|---|
class | yes | yes |
enum | yes | yes |
interface | yes | no |
type | yes | no |
function | no | yes |
var,let,const | no | yes |
例如:interface接口就不能作为值赋值,一般使用最多的就是定义类型