typescript入门学习
一. 数据类型
typescript中为了使编写的代码更规范,更有利于维护,增加了类型校验,在typescript中主要给我们提供了以下数据类型
- 布尔类型(boolean)
- 数字类型(number)
- 字符串类型(string)
- 数组类型(array)
- 元组类型(tuple)
- 枚举类型(enum)
- 任意类型(any)
- null 和 undefined
- void类型
- never类型
注意!
在typescript中, 变量的数据类型一旦发生确定一般情况下就不能发生更改, any类型以及混合类型除外
如
var str:string='this is ts';
str='haha'; //正确
str=true; //错误
或者
var str='this is ts';
str='haha'; //正确
str=true; //错误
定义类型不进行赋值也会报错
var str:string;
console.log(str) // 直接报错
1. 数组类型的定义方式
-
第一种定义方式
var arr:number[]=[11,22,33];
-
第二种定义方式
var arr:Array<number>=[11,22,33]; // 这种方式较为常用
2. 元组类型
值得一提的是: 元组类型是数组类型的一种
当存储的元素数据类型不同,则需要使用元组。(即一个数组里面既有数字类型, 又有字符串类型)
定义方式:
let arr:[number,string]=[123,'this is ts'];
3. 可枚举类型
事先考虑到某一变量可能取的值,尽量用自然语言中含义清楚的单词来表示它的每一个值,
这种方法称为枚举方法,用这种方法定义的类型称枚举类型。
一般用来配置唯一标识符
语法:
enum 枚举名{
标识符[=整型常数],
标识符[=整型常数],
...
标识符[=整型常数],
} ;
关于可枚举对象的定义以及赋值问题
举个例子:
enum Flag {success=1,error=2};
let f:Flag=Flag.error; // 注意取值定义的写法
console.log(f); // 输出2
4. void类型
void类型 :typescript中的void表示没有任何类型,一般用于定义方法的时候方法没有返回值。
举个例子
//正确写法
function run():void{
console.log('run')
}
run();
//错误写法
function run():undefined{
console.log('run')
}
run();
// 当明确返回类型时
function run():number{
return 123;
}
run();
5. never类型
never类型:是其他类型 (包括 null 和 undefined)的子类型,代表从不会出现的值。这意味着声明never的变量只能被never类型所赋值。
6. 不确定类型
一个元素可能是 number类型 可能是null 可能是undefined; 可以进行如下的定义
var num:number | null | undefined;
此时当定义 num变量但没有赋值时, num类型就是undefined, 而不是直接报错
二. 函数
1. 定义函数
声明函数
// 正确写法
function run():string{
return 'run';
}
//错误写法
function run():string{
return 123;
}
匿名函数
var fun2=function():number{
return 123;
}
alert(fun2()); /*调用方法*/
2. 函数传参
// 声明式函数传参以及定义返回值
function getInfo(name:string,age:number):string{
return `${name} --- ${age}`;
}
alert(getInfo('zhangsan',20));
// 匿名函数传参以及定义返回值
var getInfo=function(name:string,age:number):string{
return `${name} --- ${age}`;
}
alert(getInfo('zhangsan',40));
// 函数没有返回值
function run():void{
console.log('run')
}
run();
可选参数
// 用 形参名?type的形式, 表名参数是可选参数
function getInfo(name:string,age?:number):string{
if(age){
return `${name} --- ${age}`;
}else{
return `${name} ---年龄保密`;
}
}
alert(getInfo('zhangsan'))
alert(getInfo('zhangsan',123))
注意:可选参数必须配置到参数的最后面
默认参数
function getInfo(name:string,age:number=20):string{
if(age){
return `${name} --- ${age}`;
}else{
return `${name} ---年龄保密`;
}
}
alert( getInfo('张三'));
alert( getInfo('张三',30));
剩余参数
三点运算符 接受形参传过来的值
function sum(...result:number[]):number{
var sum=0;
for(var i=0;i<result.length;i++){
sum+=result[i];
}
return sum;
}
alert(sum(1,2,3,4,5,6)) ;
// 或者已明确几个参数, 其他参数剩余
function sum(a:number,b:number,...result:number[]):number{
var sum=a+b;
for(var i=0;i<result.length;i++){
sum+=result[i];
}
return sum;
}
alert(sum(1,2,3,4,5,6)) ;
3. 函数重载
typescript中的重载:通过为同一个函数提供多个函数类型定义来试下多种功能的目的。
function getInfo(name:string):string;
function getInfo(name:string,age:number):string;
function getInfo(name:any,age?:any):any{
if(age){
return '我叫:'+name+'我的年龄是'+age;
}else{
return '我叫:'+name;
}
}
4. 箭头函数
与js 一样, 箭头函数里面的this指向上下文
setTimeout(()=>{
alert('run') //
},1000)
三. 类
1. ES5原生JS继承
function Person(name,age){
this.name=name; /*属性*/
this.age=age;
this.run=function(){ /*实例方法*/
alert(this.name+'在运动');
}
}
Person.prototype.sex="男";
Person.prototype.work=function(){
alert(this.name+'在工作');
}
function Web(name,age){
Person.call(this,name,age); //对象冒充继承 可以继承构造函数里面的属性和方法、实例化子类可以给父类传参
}
Web.prototype=Person.prototype;
var w=new Web('赵四',20);
w.run(); // 输出 赵四在运动
2. 定义类
class Person{
// 属性方法的权限修饰符默认是 public
name:string; //属性 前面省略了public关键词
constructor(n:string){ //构造函数 实例化类的时候触发的方法
this.name=n;
}
run():void{
alert(this.name);
}
}
var p=new Person('张三');
p.run()
3. ts中实现继承
class Person{
name:string;
constructor(name:string){
this.name=name;
}
run():string{
return `${this.name}在运动`
}
}
var p=new Person('王五');
alert(p.run())
class Web extends Person{
// 即 ES5中对象冒充对象实现继承父类传参
constructor(name:string){
// super函数可以不传参
super(name); /*初始化父类的构造函数*/
}
}
var w=new Web('李四');
alert(w.run());
关于继承子类重写父类方法
class Person{
// ts中定义类属性的方法
name:string;
constructor(name:string){
this.name=name;
}
run():string{
return `${this.name}在运动`
}
}
var p=new Person('王五');
alert(p.run())
class Web extends Person{
constructor(name:string){
super(name); /*初始化父类的构造函数*/
}
// run重写了继承父类的 run方法
run():string{
return `${this.name}在运动-子类`
}
work(){
alert(`${this.name}在工作`)
}
}
var w=new Web('李四');
alert(w.run());
w.work();
alert(w.run());
4. 权限修饰符
- public :公有 在当前类里面、 子类 、类外面都可以访问
- protected:保护类型 在当前类里面、子类里面可以访问 ,在类外部没法访问
- private :私有 在当前类里面可以访问,子类、类外部都没法访问
这三者的权限 public > protected > private
5. 静态属性
5.1 原生JS ES5定义静态方法和静态属性
function Person(){
this.run1=function(){
}
}
Person.name='哈哈哈';
Person.run2=function(){ // 静态方法
}
var p=new Person();
Person.run2(); // 静态方法的调用
5.2 ts定义静态方法和静态属性
class Per{
public name:string;
public age:number=20;
//静态属性
static sex="男";
constructor(name:string) {
this.name=name;
}
run(){ /*实例方法*/
alert(`${this.name}在运动`)
}
work(){
alert(`${this.name}在工作`)
}
static print(){ /*静态方法 里面没法直接调用类里面的属性*/
alert('print方法'+Per.sex);
}
}
var p=new Per('张三');
p.run();
Per.print(); // 调用静态方法
alert(Per.sex); // 调用静态属性
6. 多态
多态:父类定义一个方法不去实现,让继承它的子类去实现 每一个子类有不同的表现
与抽象类不同, 并不需要强制实现
多态属于继承
class Animal {
name:string;
constructor(name:string) {
this.name=name;
}
eat(){
//具体吃什么 不知道 , 具体吃什么?继承它的子类去实现 ,每一个子类的表现不一样
console.log('吃的方法')
}
}
class Dog extends Animal{
constructor(name:string){
super(name)
}
// 重写继承父类的方法
eat(){
return this.name+'吃粮食'
}
}
class Cat extends Animal{
constructor(name:string){
super(name)
}
// 重写继承父类的方法
eat(){
return this.name+'吃老鼠'
}
}
7. 抽象类
抽象类:它是提供其他类继承的基类,不能直接被实例化。
abstract 定义的抽象类不能实例化, 只能用于被继承
abstract class Animal{
public name:string;
constructor(name:string){
this.name=name;
}
abstract eat():any; //抽象方法不包含具体实现并且必须在派生类中实现。
run(){
console.log('其他方法可以不实现')
}
}
/*错误的写法*/
var a=new Animal(); // 编译直接报错
上述的抽象类一般用法
class Dog extends Animal{
//抽象类的子类必须实现抽象类里面的抽象方法
constructor(name:any){
super(name)
}
// 重写抽象类(更准确说实现抽象类)
eat(){
console.log(this.name+'吃粮食')
}
}
var d=new Dog('小花花');
d.eat();
class Cat extends Animal{
//抽象类的子类必须实现抽象类里面的抽象方法
constructor(name:any){
super(name)
}
run(){
}
// 重写抽象类(更准确说实现抽象类)
eat(){
console.log(this.name+'吃老鼠')
}
}
var c=new Cat('小花猫');
c.eat();
继承抽象类的抽象方法, 必须实现, 不然直接编译报错
四. 接口
接口:行为和动作的规范,对批量方法进行约束
接口的作用:在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。
1. 属性接口
// 定义 对象属性接口
interface FullName{
firstName:string; //注意以;为结束, 不是js中以,为结束
secondName:string;
}
function printName(name:FullName){
// 必须传入对象 firstName secondName
console.log(name.firstName+'--'+name.secondName);
}
printName('1213'); // 错误 传入的参数必须是一个对象
/*传入的参数必须包含 firstName secondName*/
var obj={
age:20,
firstName:'张',
secondName:'三'
};
printName(obj)
2. 可选属性接口
interface FullName{
firstName:string;
secondName?:string;
}
function getName(name:FullName){
console.log(name)
}
getName({
firstName:'firstName' // 其中secondName可以有也可以没有, 但一旦存在则必须是字符串类型
})
3. 属性接口应用
// 定义配置信息对象的接口
interface Config{
type:string;
url:string;
data?:string; // data属性可选
dataType:string;
}
//原生js封装的ajax
function ajax(config:Config){
var xhr=new XMLHttpRequest();
xhr.open(config.type,config.url,true);
xhr.send(config.data);
xhr.onreadystatechange=function(){
if(xhr.readyState==4 && xhr.status==200){
console.log('success');
if(config.dataType=='json'){
console.log(JSON.parse(xhr.responseText));
}else{
console.log(xhr.responseText)
}
}
}
}
// 调用接口
ajax({
type:'get',
data:'name=zhangsan', // 此处的data可有可无
url:'http://a.itying.com/api/productlist', //api
dataType:'json'
})
4. 函数类型接口
函数类型接口: 对方法传入的参数以及返回值进行约束 批量约束
interface encypt{
(key:string, value:string):string; // 注意!, 这里是分号
}
// md5:encypt 规定了 md5这个函数参数类型以及返回类型
var md5:encypt = function (key, value):string{
return key + ' ' + value // 模拟加密操作
}
5. 可索引接口
**可索引接口: **:数组、对象的约束
首先先回忆一下TS中定义数组的方式
// 第一种方法
var dataArr:number[] = [1, 2, 3, 4];
// 第二种方法
var dataArr1:Array<number> = [1, 2, 3, 4]
上述中的 number[]
和Array<number>
可以看成是一个接口
-
对数组的约束
interface UserArr{ // 数组的索引是数字类型, 对应的值是字符串类型 [index:number]:string } var arr:UserArr=['aaa','bbb']; console.log(arr[0]); var arr:UserArr=[123,'bbb']; /*错误, 上述接口规定, 数组不能是数字 */ console.log(arr[0]);
-
对对象的约束
interface UserObj{ [index:string]:string // 定义索引值类型为string, 内容为string类型 } var arr:UserObj={name:'张三'}; var arr1:UserObj = {name: 20}; // 报错
6. 类类型接口
**类类型接口: **对类的约束 和 抽象类抽象有点相似
// 定义类的接口
interface Animal{
// 定义类的属性
name:string;
// 定义类的方法
eat(str:string):void;
}
// 接口的实现, 与抽象类不同, 接口使用的是implements, 并且继承接口必须实现出来
class Dog implements Animal{
name:string;
constructor(name:string){
// 实现属性
this.name=name;
}
// 实现方法
eat(){
console.log(this.name+'吃粮食')
}
}
var d=new Dog('小黑');
d.eat();
class Cat implements Animal{
name:string;
constructor(name:string){
// 实现属性
this.name=name;
}
// 实现方法
eat(food:string){
console.log(this.name+'吃'+food);
}
}
var c=new Cat('小花');
c.eat('老鼠');
7. 接口扩展
**接口扩展: **接口可以继承接口
// 定义父类接口
interface Animal{
eat():void;
}
// 子类继承父类接口, 使用 extends关键字
interface Person extends Animal{
work():void;
}
// 实体类实现接口子类接口
class Web implements Person{
public name:string;
constructor(name:string){
this.name=name;
}
// 实现父类接口(必须)
eat(){
console.log(this.name+'喜欢吃馒头')
}
// 实现子类接口(必须)
work(){
console.log(this.name+'写代码');
}
}
var w=new Web('小李');
w.eat();
五. 泛型
泛型就是解决 类 接口 方法的复用性、以及对不特定数据类型的支持(类型校验)
1. 定义泛型
首先回忆一下, TS中对函数返回类型的定义
// 只能返回string类型的数据
function getData(value:string):string{
return value;
}
而当我们在不同场景下使用该函数, 既有可能返回数字类型, 又有可能返回字符串类型, 此时就必须用到函数的重载
// 同时返回 string类型 和number类型
function getData1(value:string):string{
return value;
}
function getData2(value:number):number{
return value;
}
当然了上述的代码就显得有点冗余, 用any
数据类型确实可以解决, 但类型限制的意义又没有了
,基于此开始使用泛型的定义;
泛型:可以支持不特定的数据类型
要求:传入的参数和返回的参数一致
T
表示泛型,具体什么类型是调用这个方法的时候决定的, 定义的时候是不明确具体的数据类型, 也就是说, 可以不同<T>
表示用<A>
或<B>
都可以
// <T> 表示这个函数(方法)是一个泛型, 传入参数与返回参数必须一致
function getData<T>(value:T):T{
return value;
}
getData<number>(123);
getData<string>('1214231');
/*错误的写法*/
getData<number>('2112'); // <T>泛型定义的是number类型 传入的却是string类型,
function getData<T>(value:T):any{
return '2145214214';
}
getData<number>(123); //参数必须是number,
getData<string>('这是一个泛型'); // 参数必须是string
值得注意一点
function getData<T>(value:T):T{
return "22323211";
}
// 错误, 定义的是泛型,返回的就必须是泛型而不是数据类型, 即类型不能明确,返回类型可以用any表示
getData<string>('1214231');
2. 泛型类
// 需要同时支持返回数字和字符串 a - z两种类型
class MinClass{
public list:number[]=[];
add(num:number){
this.list.push(num)
}
min():number{
var minNum=this.list[0];
for(var i=0;i<this.list.length;i++){
if(minNum>this.list[i]){
minNum=this.list[i];
}
}
return minNum;
}
}
var m=new MinClass();
m.add(3);
m.add(22);
m.add(23);
m.add(6);
m.add(7);
alert(m.min());
上述类只能实现对number类型的传入与返回, 不支持字符串, 用类的泛型进行改装
//类的泛型
class MinClas<T>{
public list:T[]=[];
// 也可以这么描述
// public list:Array<T> = [];
add(value:T):void{
this.list.push(value);
}
min():T{
var minNum=this.list[0];
for(var i=0;i<this.list.length;i++){
if(minNum>this.list[i]){
minNum=this.list[i];
}
}
return minNum; // 此处返回的是通过 list:T[]定义的泛型
}
}
/*实例化类 并且制定了类的T代表的类型是number*/
var m1=new MinClas<number>();
m1.add(11);
m1.add(3);
m1.add(2);
alert(m1.min())
/*实例化类 并且制定了类的T代表的类型是string*/
// 泛型类实例化的规范 : new Class<T>()
var m2=new MinClas<string>();
m2.add('c');
m2.add('a');
m2.add('v');
alert(m2.min())
3. 泛型接口
先来回忆一下函数类型接口
// 定义接口
interface ConfigFn{
(value1:string,value2:string):string;
}
// 实现接口, value1必须存在且类型为string, 以此类推
var setData:ConfigFn=function(value1:string,value2:string):string{
return value1+value2;
}
setData('name','张三');
泛型定义的接口
// 定义泛型接口
interface ConfigFn{
<T>(value:T, value2: T):T; // 规定这个value值是一个泛型
}
// 实现泛型接口
var getData:ConfigFn=function<T>(value:T, value2: T):T{
return value+value2;
}
// 调用实现了泛型接口的函数
getData<string>('张三', "李四");
getData<string>(1243, "张三"); //错误, 定义string传入number
还有一种定义泛型接口方法:
interface ConfigFn<T>{
(value:T):T;
}
// 定义函数类型泛型
function getData<T>(value:T):T{
return value;
}
// 具名函数的变量中定义泛型
var myGetData:ConfigFn<string>=getData;
myGetData('20'); /*正确*/
// myGetData(20) //错误
六. 接口以及泛型的简单应用
场景: 定义一个操作数据库的类, 支持 mysql, MongoDB
**要求: ** mysql和mongodb的功能一致, 都有, add(), update, delete, get方法
// 首先定义操作数据方法的接口
// 因为接口不知道具体是mysql还是mongodb, 因此需要传入一个泛型
interface DBI<T> {
add(info: T): boolean;
update(info: T):boolean;
delete(id:number): boolean;
get(id:number):Array<any>; // 注意接口的定义是用 ;分开来的
}
// 定义一个叫mysql数据库的类, 实现接口
// 传入实现的泛型类
// 注意, 要实现泛型接口, 实现的类也应该是一个泛型类
// 提示: Generic type 'DBI<T>' requires 1 type argument(s), 接口需要传入一个泛型
class MysqlDb<T> implements DBI<T> {
add(info: T): boolean {
console.log(info);
return false;
}
delete(id: number): boolean {
return false;
}
get(id: number): Array<any> {
return undefined;
}
update(info: T): boolean {
return false;
}
}
// 操作一个用户表, 定义一个User类和数据库映射
class User {
username: string | undefined;
password: string | undefined;
}
const U = new User();
U.username = "wang";
U.password = "123456";
const Mysql1 = new MysqlDb<User>(); // 类作为参数来约束数据传入的类型
// Mysql1.add(U);
/*Mysql1.add({
username: "wang",
password: "123456"
});
// 也可以这么传值
*/
/*Mysql1.add({
username1: "wang",
password: "123456"
// 编译报错, 传入内容必须符合User类规范
});*/
七. 装饰器
1. 类装饰器
类装饰器:类装饰器在类声明之前被声明(紧靠着类声明)。
类装饰器应用于类构造函数,可以用来监视,修改或替换类定义
// 不传参
function logClass(params: any) {
console.log(params); // 输出 Function: Decoration1 指装饰器指向的类
params.prototype.run = function ():void {
console.log("在奔跑")
}
}
@logClass
class Decoration1 {
constructor(){
}
getData(){
}
}
const d1:any = new Decoration1(); // 实例化的类型为 any类型
d1.run(); // 输出 "在奔跑"
// 传参, 装饰器工厂
// 定义装饰器工厂
function logClass(params:string) {
return function (target: any) {
console.log(params); // 输出 https://wangermaizi.github.io
console.log(target); // 输出 Function: Decoration1
target.prototype.baseUrl = params;
}
}
// 加载装饰器
@logClass('https://wangermaizi.github.io')
class Decoration1{
constructor(){
// console.log(Decoration1.baseUrl); // 报错,找不到装饰器上的baseUrl
}
getData(){
}
}
const d1:any = new Decoration1();
console.log(d1.baseUrl); // 输出 https://wangermaizi.github.io
由上可知, 类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数
2. 装饰器重载构造函数
// 定义装饰器
function logClass(target: any) {
return class extends target{
baseUrl:any = "222222222";
get() {
console.log("2222221111111")
}
}
}
@logClass // 注意!!! 装饰器不传参的时候不要加上括号
class Decoration1 {
public baseUrl:string | undefined;
constructor() {
this.baseUrl = "1111111"
}
get() {
console.log("1111122222222")
}
}
const d1:any = new Decoration1();
d1.get(); // 输出的是装饰器方法
console.log(d1.baseUrl); // 输出装饰器的属性
3. 属性装饰器
属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
2、成员的名字。
// 属性装饰器
function logProperty(params:any){
return function(target:any,attr:any){
console.log(target); // 输出 指向的当前类 --> Decoration1
console.log(attr); // 输出 指向装饰的属性--> url
target[attr]=params;
}
}
class Decoration1 {
@logProperty("wang")
public url : string | undefined;
constructor() {
}
getData () {
console.log(this.url)
}
}
const d1:any = new Decoration1();
d1.getData();
4. 方法装饰器
方法装饰会在运行时传入下列3个参数:
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
2、成员的名字。
3、成员的属性描述符。
function get(params: any) {
return function (target: any, methodName: any, desc: any) {
console.log(target); // 装饰器指向的当前类, 里面包含着类型的属性
console.log(methodName); // 装饰的方法名
console.log(desc); // 输出描述
/*
* {
value: [Function: getData],
writable: true,
enumerable: false,
configurable: true
}
* */
// 往当前类中追加一个方法
target.baseUrl = "https://github.io";
// 往当前类中追加一个方法
target.run = function () {
console.log("running");
console.log(this.url); // 输出 undefined
console.log(this.base); // 输出 undefined
console.log(desc.value); // 输出 getData方法
};
const oldMethod = desc.value;
// 修改装饰器中的方法, 把getData方法里面传入的参数改为string类型
desc.value = function (...args:Array<any>) {
// 遍历形参数组, 将数组中的每一项变为string类型
args = args.map((value, index, array)=>{
return String(value);
});
// 打印出所有的参数查看结果
console.log(args); // 输出 [ '11', '22', '13', '11' ]
console.log(params); // 可以正常输出传参内容
oldMethod.apply(this, args) // 此时就可以调用原版中的getData方法
}
// 但是此时方法已经被装饰器所替换, 想要修改而不是替换, 就需要进行对象冒充
}
}
function logPrototype(params: any) {
return function (target: any, attr: any) {
target[attr] = params;
}
}
class Decoration1 {
@logPrototype("https://wangermaizi.github.io")
public url: string | undefined;
constructor() {}
@get("https://wangermaizi.github.io")
getData() {
console.log(this.url); // this.url需要通过属性装饰进行修改, 不能方法装饰器中修改
}
}
const d1:any = new Decoration1();
5. 参数装饰器
参数装饰器表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元素数据 ,传入下列3个参数:
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
2、方法的名字。
3、参数在函数参数列表中的索引。
6. 装饰器执行顺序
属性 => 方法 => 方法参数 => 类
如果有多个同样的装饰器,它会先执行后面的
网友评论