这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战
前段时间发表了node的学习
现在丸子终于要对ts进行总结跟回顾了
目录
- 函数
- 类型约束
- 函数重载
- this问题
- 类
- .静态成员
- 继承
- 抽象类
- 接口基础
- 接口可选结构
- 接口检测约束
- 接口索引签名
- 接口函数类型接口
- 类接口
- 案例
typescript是javascript的超集,在js的基础上,为你的变量参数等加上类型定义,于是就变成了typescript。
下面都是代码+注释结合的学习总结。不多废话,直接看吧。
1 &2 typscirpt的函数声明和类型约束
/**
* 函数
* 函数声明
* 函数表达式
*
* 类型约束
* 函数参数
* 函数返回值
*
* 如果函数没有返回值,使用 void,不是undefined
* // function fn1(x: number, y: number): number {
// return x + y;
// }
// fn1(1,2);
// fn1('a', 'b');
// 函数表达式
// let fn2 = function(x: number, y: number): number {
// return x + y;
// }
// let a: number = 1;
// let fn2: (x: number, y: number) => number = function(x: number, y: number): number {
// return x + y;
// }
// 根据类型推断可以简写
// let fn2: (x: number, y: number) => number = function(x, y) {
// return x + y;
// }
// fn2('a', 'b');
// fn2('a', 'b');
// fn2(1, 2);
// 可选参数
// function fn3(x: number, y?: number): void {}
// fn3(1);
// 参数默认值
// function fn3(x: number, y = 1): void {
// console.log(y);
// }
// fn3(0);
// 剩余参数
// function fn4(...args: any[]) {
// console.log(args);
// }
*/
这里看齐啦typescript跟普通javascript好像差别不大啊。
好,下面继续看。
3 .函数重载
// 如果是在js中
// function fn(x, y) {
// return x + y;
// }
// fn(1, 2);
// fn('a', 'b');
// 函数重载
// function fn(x: number, y: number): number;
// function fn(x: string, y: string): string;
// function fn(x: any, y: any): any {
// return x + y;
// }
// fn(1, 2);
// fn('a', 'b');
// fn(1, 'a');
4 ts里面的this
/**
* ts中默认情况下函数中的this默认指向 : any
*/
let obj = {
a: 10,
fn() {
// 因为默认情况下,this是any类型,any类型ts不能提示有任何属性方法
// let document:any;
// any的值,ts不能提示或者进行类型属性检测
// console.log(this.b);
// 使用noImplicitThis选项可以取消默认this的any来这个设置
// this.a
}
}
// obj.fn();
// ts会自动推导事件函数中的this
// document.onclick = function() {
// this
// }
let obj1 = {
a: 1,
fn(this: Element|Document) { // 在ts中函数的第一个this参数是用来设置this类型约束的
// 这个this是一个假参数,运行过程中是不存在,是给ts检测使用的
// console.log(this);
// 希望ts是按照事件函数中的this去做检测
this; //检测检测检测
// this
}
};
document.onclick = obj1.fn;
document.body.onclick = obj1.fn;
// function fn10(x: number) {
// }
// fn10( document.querySelector('input').value );
唯一需要特别注意的是typescript 中默认情况下函数内的this指向是any
5 ts中的类
类是对事物的通用概括,也就是通用结构的概括。
运行设置公开访问的属性和方法,也允许设置保护的属性和保护的方法,甚至是私有的属性和方法(也就是对外部完全不开放访问的代码)。
下面看一下代码:
// class Person {
// /**
// * ts中的类,成员属性必须要声明后使用
// * ts中类的成员属性不是在构造函数中声明的,是在class内,方法外
// *
// * public
// * 公开的,所有的地方都能访问,属性和方法默认是public
// * protected
// * 受保护的,在类的内部和他的子类中才能访问
// * private
// * 私有的,只能在该对象(类)的内部才可以访问
// */
// public username: string = '';
// // private username: string = '';
// // protected username: string = '';
// // readonly username: string = '';
// constructor(name: string) {
// this.username = name;
// }
// }
// class Student extends Person {
// say() {
// this.username
// }
// }
// let p1: Person = new Person('Kimoo');
// p1.username = 'zmouse';
class Person {
username: string = 'Kimoo';
// private age: number = 10;
private _age: number = 10;
// getAge(): number {
// return this.age;
// }
// setAge(age: number): void {
// if (age > 0 && age < 150) {
// this.age = age;
// }
// }
// 存取器,这个a并不会作为方法,而是属性去访问
get age(): number {
return this._age;
}
set age(age: number) {
if (age > 0 && age < 150) {
this._age = age;
}
}
}
let p1: Person = new Person();
/**
* 允许在外部获取和修改age的值,但是不希望该修改成非法的,比如1000岁
*/
// p1.age = 1100;
// console.log(p1);
// p1.setAge(20);
// p1.setAge(200);
// console.log(p1.getAge());
// console.log( p1.a );
上面的类和方法调用比较像Java中的的风格。
6 类的静态成员
单例是一种设计模式,它的作用是让程序进程中只有一份对象实例。
下面,我们看看单例的实现代码。
/**
* 单例
*/
// class Mysql {
// host: string;
// port: number;
// username: string;
// password: string;
// dbname: string;
// constructor(host = '127.0.0.1', port = 3306, username='root', password='', dbname='') {
// this.host = host;
// this.port = port;
// this.username = username;
// this.password = password;
// this.dbname = dbname;
// }
// query() {}
// insert() {}
// update() {}
// }
/**
* 创建一个Mysql对象,通过这个对象来操作数据库
* 如果我们不加以限制的话,这个Mysql是可以new出来多个对象的
* 每一个Mysql都会占用资源(内存)
*/
// let db = new Mysql();
// db.query();
// db.insert();
// let db1 = new Mysql();
// db1.query();
// db1.insert();
/**
* 通过某种方法控制系统同时只有一个Mysql的对象在工作
* 通过口头去约定是不靠谱的
*/
class Mysql {
// 静态属性,不需要通过new出来的对象方面,直接是通过Mysql类来访问
public static instance;
host: string;
port: number;
username: string;
password: string;
dbname: string;
private constructor(host = '127.0.0.1', port = 3306, username='root', password='', dbname='') {
this.host = host;
this.port = port;
this.username = username;
this.password = password;
this.dbname = dbname;
}
public static getInstance() {
if (!Mysql.instance) {
Mysql.instance = new Mysql();
}
return Mysql.instance;
}
query() {}
insert() {}
update() {}
}
// let db = new Mysql();
// console.log(Mysql.instance);
let db = Mysql.getInstance();
db.query();
db.insert();
这里展示的单例是一个操作Mysql数据库的一个对象实例。
这样保证了在内存中只有一个Mysql实例对象,不管是什么时候调用getInstance方法,我们获得一个唯一的Mysql操作的对象。
这样的好处可以保证进程中只有这个操作对象,统一管理的入口。
7.继承
继承是面向对象的特征之一。
这个在提高代码的重用性有很大作用。
先看代码:
class Person {
private _a = 1;
// 在构造函数的参数中如果直接使用public等修饰符,则等同于同时创建了该属性
constructor(public username: string, public age: number) {
this.username = username;
this.age = age;
}
}
class Student extends Person {
// 如果子类没有重写构造函数,则直接父类的
// 如果子类重写了构造函数
// 注意:需要手动调用父类构造函数
// super:关键字,表示父类
constructor(username: string, age: number, public type: string) {
super(username, age); //执行父类构造函数
this.type = type;
}
}
let s1 = new Student('Kimoo', 30, 'javascript');
从代码我们可看到,继承一下子省略了很多代码。
直接把一个类型的属性,方法复用了。
这样我们想要对一个类型演化的多个类型就很好编程实现,也提高了一个追溯的脉络。
通过继承,我们可以发现整个类型结构的树,从子类型追溯到祖宗类型。
8.抽象类
抽象类,就是类中允许定义抽象的方法。
代码如下:
abstract class Person { //抽象类不能实例化的
username: string;
constructor(username: string) {
this.username = username;
}
say() {
console.log('哈哈哈哈哈');
}
// 虽然子类都会有这样的特性,学习,但是子类学习具体过程不一 样,所在在父类确定
// 不了study方法的具体实现,父类只能有抽象约定,接收什么参数,返回什么内容
// 如果一个类中有抽象的方法了,那么这个类也必须是抽象的
abstract study(): void //抽象方面是没有具体代码的
}
class Student extends Person {
study() {
console.log('学生有学生的学习方法 - 需要老师教授');
}
}
class Teacher extends Person {
study() {
console.log('自学');
}
}
// 如果一个类继承了抽象的父类,就必须实现所有抽象方面,否则这个子类还是必须得为抽象的
// abstract class P extends Person {
// }
// new Person();
有什么用呢?
抽象类允许局部不实现,这样实现了整体约束的情况下,支持了差异化衍生的特性。
这样我们可以通过抽象类实现统一调用一个先不实现的方法(抽象函数)。
然后不同的子类实现该抽象方法,达到统一的调用,但是执行的细节不一样。
9 接口 interface
接口是区别于类的数据结构声明方式。
跟类区别的是,接口中不允许实现,一切只能是声明。
这样有啥好处呢?
没有声明具体的方法实现,那不是空白?不是的。这样比类更加弹性。
我们可以用一个接口去承载不同的实现类,然后程序上面达到统一的调用。
这样能够写出更多通用的代码,提高了组件的重用率。
下面是接口的定义
// /**
// * interface
// * 为我们提供一种方式来定义某种结构,ts按照这种结构来检测数据
// *
// * 写法
// * interface 接口名称 {
// * // ... 接口规则
// * }
// *
// * 接口中定义的规则只有抽象描述,不能有具体的值与实现的
// *
// * 对象抽象 => 类(对象的抽象描述)
// * 类抽象 => 抽象类(如果一个类中拥有一个没有具体实现的抽象方法,就是抽象类)
// * 抽象类 => 接口(如果一个抽象类的成员全部是抽象的,那么可以看做接口)
// */
// interface Options {
// width: number,
// height: number
// }
// function fn(opts: Options) {}
// // 类型检测只检测必须的属性是否存在,不会按照顺序进行,无序的
// fn({
// height: 200,
// width: 100
// });
10 接口中的可选结构
有些时候我们需要达到一定的限制的情况下,又要允许某些字段/属性不给值。
这个时候,我们可以使用'?‘ 来修饰属性。这样的话,编译器不会强制检测某个字段一定要给值/赋值。
下面看一下展示代码:
// /**
// * 如果规则中有些是可选的,那么通过 ? 标识
// */
// interface Options {
// width: number,
// height: number,
// color?: string
// }
// function fn(opts: Options) {}
// fn({
// height: 200,
// width: 100
// });
11 接口中的检测约束
下面展示使用约束检测的场景和绕开约束检测的场景。
这个代码不太多可以快速看一下:
// /**
// * 如果我们希望检测不必要这么复杂
// * - 如果我们希望某些时候,只要包含其中一些规则即可
// * - 通过可选 ? 来实现
// * - 通过 as 断言
// * - 通过变量转换
// */
// interface Options {
// width: number,
// height: number,
// color: string
// }
// function fn(opts: Options) {}
// // 告知ts检测,我传入的就是一个Options
// // fn({
// // height: 200,
// // width: 100
// // } as Options);
// // 先赋值给一个变量,也可以绕开 检测
// // let obj = {
// // height: 200,
// // width: 100,
// // color: 'red',
// // a: 1
// // }
// // fn( obj );
12 索引签名
这个技术更多是对类型/接口某一个或者多个字端的类型进行限定。
这样的好处很明显,可以直接在编译代码的阶段拒绝不良的数据类型,直接编译失败。
而不是程序在运行的过程才报错,这个时候改bug的代价是很高的!!!
下面是展示代码:
// /**
// * 希望规则是:一组由数字进行key命名的对象
// * 我们可以使用索引签名
// * 为数据定义一组具有某种特性的key的数据
// * 索引key的类型只能是 number 和 string 两种
// */
// // interface Options {
// // // key 是number,value是any类型的数据
// // [attr: number]: any,
// // length: number
// // }
// // function fn(opt: Options) {}
// // fn({
// // 0: 100,
// // 1: 100,
// // 2: 2000,
// // length: 1
// // });
// interface Options {
// // 索引签名的key课是number,也可以是string
// [attr: string]: any,
// length: number
// }
// function fn(opt: Options) {}
// fn({
// a: 1,
// b: 2,
// length: 100
// });
13 函数类型接口
/**
* 这个接口描述的是一个包含有fn并且值的类型为函数的结构体,并不是描述函数结构
* 而是一个包含函数的对象结构
*/
// interface Options {
// fn: Function
// }
// let o: Options = {
// fn: function() {}
// }
// let fn: (x: number, y: number) => number = function(x: number, y: number): number {return x + y}
/**
* 定义一个事件函数,那么这个函数必须得有一定的规则了
* 我们不能随便的把一个函数赋值给事件
*/
// function fn(x: MouseEvent) {
// console.log(x.clientX);
// }
// document.onkeydown = fn;
// 我们也可以使用 interface 来约定定义函数的结构
// 定义的是函数接口
// interface IFn {
// (x: number, y: number): number
// }
// let fn: IFn = function(x: number, y: number): number {return x + y}
// 定义了一个接受一个MouseEvent类型参数的函数结构
// interface MouseEventCallBack {
// (e: MouseEvent): any
// }
// let fn: MouseEventCallBack = function(a: MouseEvent) {
// }
// document.onclick = fn;
// interface ResponseCallBack {
// (rs: Response): any
// }
// function todo(callback: ResponseCallBack) {
// callback(new Response);
// }
// todo(function(x: string) {
// });
// fetch('url').then( (a: Response) => {
// a.json();
// } );
// interface AjaxData {
// code: number;
// data: any
// }
// interface AjaxCallback {
// (rs: AjaxData): any
// }
// function ajax(callback: AjaxCallback) {
// callback({
// code: 1,
// data: []
// });
// }
// ajax( function(x: AjaxData) {
// x.code
// x.data
// x.message
// } );
函数类型的接口,这个可以实现动态传入参数的类型约束。
就像上面的代码一样,我们定义了x类型为AjaxData类型。
这样就保证了参数的结构,必须符合指定的接口,否则报错。
而且接收的函数参数也是接口类型。
这样就实现不同动作(函数)执行结构的约束。
14 类接口
有时候,我们需要使用接口让某个类去符合某种契约,达到强约束的情况下弹性的输入。
这个怎么做呢?
先看代码:
/**
* 类接口
* 使用接口让某个类去符合某种契约
*
* 类可以通过 implements 关键字去实现某个接口
* - implements 某个接口的类必须实现这个接口中确定所有的内容
* - 一个类只能有一个父类,但是可以implements多个接口,多个接口使用逗号分隔
*/
/**
* 类接口
* 使用接口让某个类去符合某种契约
*
* 类可以通过 implements 关键字去实现某个接口
* - implements 某个接口的类必须实现这个接口中确定所有的内容
* - 一个类只能有一个父类,但是可以implements多个接口,多个接口使用逗号分隔
*/
interface ISuper {
fly(): void;
}
class Man {
constructor(public name: string) {
}
}
class SuperMan extends Man implements ISuper {
fly() {
console.log('起飞');
}
}
class Cat {
}
class SuperCat extends Cat implements ISuper {
fly() {
console.log('起飞');
}
}
let kimoo = new SuperMan('Kimoo');
15 实战案例展示
下面是使用纯js来实现的请求处理。
js的案例如下
function http(options) {
let options = Object.assign({
method: 'get',
url: '',
isAsync: true
}, options);
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open(options.method, options.url, options.isAsync);
xhr.onload = function() {
resolve( JSON.parse(xhr.responseText) );
}
xhr.onerror = function() {
reject({
code: xhr.response.code,
message: '出错了'
});
}
xhr.send();
})
}
http()
http({
methods: 'get',
url: '....',
isAsync: true
});
http({
methods: 'post',
url: '....',
isAsync: true
});
上面我们封装了http请求,然后调用了get和post。通过分别传递不同的http options来实现get,post请求的调用。
ts实现展示
换成ts的写法
interface HttpOptions {
method: string,
url: string,
isAsync: true
}
interface HttpResponseData {
code: number,
data: any
}
function http(options: HttpOptions) {
let opt:HttpOptions = Object.assign({
method: 'get',
url: '',
isAsync: true
}, options);
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open(opt.method, opt.url, opt.isAsync);
xhr.onload = function() {
let data: HttpResponseData = JSON.parse(xhr.responseText);
resolve( data );
}
xhr.onerror = function() {
reject({
code: xhr.response.code,
message: '出错了'
});
}
xhr.send();
})
}
http({
method: 'get',
url: '....',
isAsync: true
}).then( (rs: HttpResponseData) => {
rs.code
} );
typescript我们可以使用接口来限定参数和响应结果的结构。
这样带来了更加严格的检查,不好的地方就说没有像原声的js那样弹性,多几个字段属性也不会报错。
好处的地方恰恰就是对输入的严格限制,这样避免了无用数据的污染。
也更好的把输入和输出进行了限制。而不是给一个option,很多时候这个在项目后期的维护上是很复杂的。
看吧,这就是typescript的魅力了。它的诞生还是很有必要的。
注解
注解(装饰器)是一类特殊类型的声明,可以用在类、方法、构造器、属性和参数上。
其实本质上,定义一个注解,就是定义一个TypeScript方法,只是该方法必须符合官方的规范。
方法分别返回符合规范的函数闭包,参数target、propertyKey、descriptor。
经过实践,这三个参数中target和propertyKey是必须的,没有的话编译过不去,descriptor可以省略。
下面简单展示一下@Controller这个注解。
// @Controller
// class IndexController {
// index() {
// }
// @router('/')
// @method('get')
// main() {
// }
// }
看到了把,使用注解节省了很多代码,而且看起来简化了很多。
总结
今天展示的内容很多,也分享了实战的代码。需要多多阅读。
就写到这里
我是丸子,每天学会一个小知识。
一个前端开发
希望多多支持鼓励,感谢