TypeScript介绍
- TypeScript 是由微软开发的一款开源的编程语言。
- TypeScript 是 Javascript 的超集,遵循最新的 ES6、Es5 规范。TypeScript 扩展了 JavaScript 的语法。
- TypeScript 更像后端 java、C#这样的面向对象语言,可以让 js 开发大型企业项目。
- 谷歌也在大力支持 Typescript 的推广,谷歌的 angular2.x+就是基于 Typescript 语法。
- 最新的 Vue 、React 也可以集成 TypeScript。
- Nodejs 框架 Nestjs、midway 中用的就是 TypeScript 语法。
- 带有类型,是说js在定义变量的时候,类型是动态的,只有在运行的时候才能知道它的具体类型,比如 number 或者 string,并且类型也是可以动态变化的,而 TypeScript 则是要求变量有确定的类型,并且在编写代码的时候就已经确定,如果把字符串赋给类型为 number ,数字类型的变量,就会出错。
为什么用 TypeScript
认识了 TypeScript 之后,可能你又有问题,为什么要学 TypeScript 呢?先看一条数据,在 stackoverflow 发起的2020年程序员调查中,TypeScript 在程序员最爱的编程语言中排在了第二位,仅次于 Rust:程序员最爱的编程语言(最新2020/5/27)
之所以大家喜欢 TypeScript,是因为:
- TypeScript 有类型检查机制,我们可以在写代码的时候就能够发现错误,比如给函数误传了类型不同的参数,那么通过 VS Code 对 TypeScript 的强力支持,我们能立刻看到错误。
- 另外 VS Code 能根据 TypeScript 的类型信息提供更好的代码提示和补全功能。
此外,对于大型项目、多人协作编写代码时,类型起到了文档的作用,可以清楚的知道我这个变量是什么类型,或者我定义的函数需要什么样的参数,我的对象里又有哪些属性。这样让代码更易于维护,这也是为什么大公司、大型项目更偏爱 TypeScript - 最后 TypeScript 入门的门槛低,只要你会 JavaScript,那么你就已经能编写 TypeScript 代码了。另外因为 JS 的快速发展,好多以前在 typescript 才能用的功能,你可能在JS 里已经用到了,所以说要学习的东西就更少了。
除了这些好处之外,它也有其他静态类型语言比如 Java/c++ 的通病,就是代码量会增加,并且有时候类型过于复杂反而使得代码显的更难阅读,不过跟它带来的优势相比,也显得不那么突出了。
TypeScript安装、编译
安装
npm install -g typescript
或者cnpm install -g typescript
或者yarn global add typescript
运行
tsc helloworld.ts
TypeScript 开发工具 Vscode 自动编译.ts 文件
- 创建 tsconfig.json 文件 tsc --init 生成配置文件
- 老版本 vscode 点击: 任务->运行任务-> tsc:监视-tsconfig.json 然后就可以自动生成代码了
- 新版本 vscode 点击: 终端->运行任务->typescript->tsc:监视-tsconfig.json 然后就 可以自动生成代码了
TypeScript中的数据类型
typescript中为了使编写的代码更规范,更有利于维护,增加了类型校验,在typescript中主要给我们提供了以下数据类型:
给变量定义数据类型有两种方式,一种是隐式的,一种是显式的
隐式
隐式类型是由 TypeScript 根据变量的值来推断类型,这样的话,代码的写法跟 JS 就一样了,但不同的是它后边不能用其他类型的值来给他重新赋值
let a = 10;
a = "hello";
// error TS2322: Type '"hello"' is not assignable to type 'number'.
显式
显式类型的定义,就跟之前运行的 TS 代码示例一样,我们用 :
+ 类型
来显式的规定,这个变量是什么类型的
-
布尔类型(boolean)
var flag:boolean = true; flag = false; console.log(flag);
-
数字类型(number)
-
字符串类型(string)
-
数组类型(array)
- 第一种写法:
var arr:number[] = [11, 22, 33]; console.log(arr);
- 第二种写法:
var arr1:Array<any> = ['11', 22, '33']; console.log(arr1);
- 第三种写法:
var arr2:any[] = ['131214',22,true]; console.log(arr2);
-
元组类型(tuple)— 属于数组的一种
let arr3:[number, string] = [123, 'this is ts']; console.log(arr3);
-
枚举类型(enum)
在其它程序设计语言中,一般用一个数值来代表某一状态,这种处理方法不直观,易读性差。
如果能在程序中用自然语言中有相应含义的单词来代表某一状态,则程序就很容易阅读和理解。也就是说,事先考虑到某一变量可能取的值,尽量用自然语言中含义清楚的单词来表示它的每一个值,这种方法称为枚举方法,用这种方法定义的类型称枚举类型。
enum Status {success=1, error=2}; let s:Status = Status.success; console.log(s);
enum Color {blue, red, orange}; let c:Color = Color.red; console.log(c); //1 如果标识符没有赋值 它的值就是下标
-
任意类型(any)
var num:any = 123; num = 'str'; num = true; console.log(num);
- 任意类型的用处(不加类型会报错)
var oBox:any=document.getElementById('box'); oBox.style.color='red';
-
对象类型(object)
var arr2:object = [11, 'asd', 1321]; console.log(arr2);
-
null 和 undefined
其他(never类型)数据类型的子类型var num:number; console.log(num) //输出:undefined 报错 var num1:undefined; console.log(num1) //输出:undefined //正确 var num2:number | undefined; num2=123; console.log(num2);
-
void类型
typescript中的void表示没有任何类型,一般用于定义方法的时候方法没有返回值。
function run():void{ console.log('run'); } run();
function run1():number{ return 123; } run1();
-
never类型
是其他类型 (包括 null 和 undefined)的子类型,代表从不会出现的值。这意味着声明never的变量只能被never类型所赋值。
var a: never; a = 123; //错误写法 a = (function () { throw new Error("错误") })(); //返回never的函数必须存在无法达到的终点 function infiniteLoop(): never { while (true) {} }
-
组合类型
如果一个变量可以有多个类型,但是又不想使用any破坏类型检查,那么可以使用组合类型,组合类型使用一条竖线,也就是或操作符来定义。let a: number | string = 10; a = "hello";
另外,组合类型也可以直接使用字面值来定义,这样就规定了一个变量的取值范围
let c: "on" | "off" = "on"; c = "off"; c = "other"; //错误
-
类型别名
这样代码看起来不太方便,并且这个组合类型只能给 a 使用,如果有一个变量 b 也可以同时是 number 或 string 的类型的话,还要重复定义这个类型。要解决这个问题,我们可以使用 type 关键字来给这个组合类型起个别名,让代码更易读,也方便其他变量使用,这里定义一个 type 名字叫 NumStr,自定义的类型名字推荐首字母大写
TypeScript中的函数
- 函数的定义
// 有返回值
function run(): string {
return 'run';
}
// 无返回值
function run(): void{
console.log('run');
}
// 箭头函数
const materials = [
'Hydrogen',
'Helium',
'Lithium',
'Beryllium'
];
console.log(materials.map((material): any => material.length));// 加不加any都可以
- 定义方法中的传参
function getInfo(name: string, age: number): string {
return `${name} --- ${age}`;
}
alert(getInfo("zhangsan", 20));
- 可选参数
es5里面方法的实参和行参可以不一样,但是ts中必须一样,如果不一样就需要配置可选参数
function getInfo(name: string, age?: number): string {
if (age) {
return `${name} --- ${age}`;
} else {
return `${name} ---年龄保密`;
}
}
alert(getInfo('zhangsan'))
alert(getInfo('zhangsan', 123))
注意:可选参数必须配置到参数的最后面
- 默认参数
es5里面没法设置默认参数,es6和ts中都可以设置默认参数
function getInfo(name: string, age: number = 20): string {
if (age) {
return `${name} --- ${age}`;
} else {
return `${name} ---年龄保密`;
}
}
// alert( getInfo('张三'));
alert(getInfo('张三', 30));
- 剩余参数
必要参数,默认参数和可选参数有个共同点:它们表示某一个参数。 有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来。 在JavaScript里,你可以使用 arguments来访问所有传入的参数。在TypeScript里,你可以把所有参数收集到一个变量里。
剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。
三点运算符 接受新参传过来的值
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));
- 函数重载
java中方法的重载:重载指的是两个或者两个以上同名函数,但它们的参数不一样,这时会出现函数重载的情况
TS中的重载:通过为同一个函数提供多个函数类型定义来试下多种功能的目的
- es5中出现同名方法,下面的会替换上面的方法
function css(config){
}
function css(config,value){
}
- ts中的重载
function getInfo(name: string): string;
function getInfo(age: number): string;
function getInfo(str: any): any {
if (typeof str === 'string') {
return '我叫:' + str;
} else {
return '我的年龄是' + str;
}
}
alert(getInfo('张三')); //正确
alert(getInfo(20)); //正确
// alert(getInfo(true)); //错误写法
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;
}
}
alert(getInfo('zhangsan')); //正确
alert(getInfo(123)); //错误
alert(getInfo('zhangsan', 20));
TypeScript中的类
- ts中类的定义
class Person {
name: string; //属性 前面省略了public关键词
constructor(n: string) { //构造函数 实例化类的时候触发的方法
this.name = n;
}
run(): void {
alert(this.name);
}
}
var p = new Person('张三');
p.run()
class Person {
name: string;
constructor(name: string) { //构造函数 实例化类的时候触发的方法
this.name = name;
}
getName(): string {
return this.name;
}
setName(name: string): void {
this.name = name;
}
}
var p = new Person('张三');
alert(p.getName());
p.setName('李四');
alert(p.getName());
- ts中实现继承 extends、 super
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 {
constructor(name: string) {
super(name); /*初始化父类的构造函数*/
}
work() {
alert(`${this.name}在工作`)
}
}
var w = new Web('李四');
alert(w.run());
alert(w.work());
- 类里面的修饰符
typescript里面定义属性的时候给我们提供了三种修饰符: public/protected/private
- public : 公有 在当前类里面、 子类 、类外面都可以访问
子类中:
class Person {
public name: string; /*公有属性*/
constructor(name: string) {
this.name = name;
}
run(): string {
return `${this.name}在运动`
}
}
}
class Web extends Person {
constructor(name: string) {
super(name); /*初始化父类的构造函数*/
}
run(): string {
return `${this.name}在运动-子类`
}
work() {
alert(`${this.name}在工作`)
}
}
var w = new Web('李四');
w.work();
类外部访问公有属性:
class Person {
public name: string; /*公有属性*/
constructor(name: string) {
this.name = name;
}
run(): string {
return `${this.name}在运动`
}
}
var p = new Person('哈哈哈');
alert(p.name);
- protected:保护类型 在类里面、子类里面可以访问 ,在类外部没法访问
子类中:
class Person {
protected name: string; /*保护属性*/
constructor(name: string) {
this.name = name;
}
run(): string {
return `${this.name}在运动`
}
}
class Web extends Person {
constructor(name: string) {
super(name); /*初始化父类的构造函数*/
}
work() {
alert(`${this.name}在工作`)
}
}
var w = new Web('李四11');
w.work();
alert(w.run());
类外外部没法访问保护类型的属性:
class Person {
protected name: string; /*保护类型*/
constructor(name: string) {
this.name = name;
}
run(): string {
return `${this.name}在运动`
}
}
var p = new Person('哈哈哈');
alert(p.name);
- private :私有 在类里面可以访问,子类、类外部都没法访问
子类中:
class Person {
private name: string; /*私有*/
constructor(name: string) {
this.name = name;
}
run(): string {
return `${this.name}在运动`
}
}
class Web extends Person {
constructor(name: string) {
super(name)
}
work() {
console.log(`${this.name}在工作`)
}
}
类里:
class Person {
private name: string; /*私有*/
constructor(name: string) {
this.name = name;
}
run(): string {
return `${this.name}在运动`
}
}
var p = new Person('哈哈哈');
alert(p.run());
- 静态属性 静态方法
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);
}
}
Per.print();
alert(Per.sex);
- 多态 抽象类
- 多态
父类定义一个方法不去实现,让继承它的子类去实现 每一个子类有不同的表现
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
eat() { //具体吃什么 不知道 , 具体吃什么?继承它的子类去实现 ,每一个子类的表现不一样
console.log('吃的方法')
}
}
class Dog extends Animal {
constructor(name: string) {
super(name)
}
eat() {
console.log(this.name + '啃骨头')
}
}
class Cat extends Animal {
constructor(name: string) {
super(name)
}
eat() {
console.log(this.name + '吃鱼')
}
}
let ANAME: string = "Tom";
var D = new Dog(ANAME);
D.eat();
var C = new Cat(ANAME);
C.eat();
-
抽象类
typescript中的抽象类:它是提供其他类继承的基类,不能直接被实例化
用abstract关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类中实现
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 + '吃粮食') } } class Cat extends Animal { //抽象类的子类必须实现抽象类里面的抽象方法 constructor(name: any) { super(name) } eat() { console.log(this.name + '吃老鼠') } } var d = new Dog('小花花'); d.eat(); var c = new Cat('小花猫'); c.eat();
TypeScript中的接口
接口的作用:在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用。
接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。
typescrip中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等
- ts中自定义方法传入参数,对json进行约束
function printLabel(labelInfo:{label:string}):void {
console.log('printLabel', labelInfo);
}
// printLabel('hahah'); //错误写法
// printLabel({name:'张三'}); //错误的写法
printLabel({label:'张三'}); //正确的写法
- 接口:行为和动作的规范,对批量方法进行约束
//就是传入对象的约束 属性接口
interface FullName {
firstName: string; //注意;结束
secondName: string;
}
function printName(name: FullName) {
// 必须传入对象 firstName secondName
console.log(name.firstName + '--' + name.secondName);
}
// printName('1213'); //错误
var obj = { /*传入的参数必须包含 firstName secondName*/
age: 20,
firstName: '张',
secondName: '三'
};
printName(obj)
- 参数的顺序可以不一样
interface FullName {
firstName: string;
secondName: string;
}
function getName(name: FullName) {
console.log(name)
}
//参数的顺序可以不一样
getName({
secondName: 'secondName',
firstName: 'firstName'
})
- 也可以设置可选属性
interface FullName {
firstName: string;
secondName?: string;
}
function getName(name: FullName) {
console.log(name)
}
getName({
firstName: 'firstName'
})
case:ajax
interface Config {
type: string;
url: string;
data?: string;
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',
url: 'http://a.itying.com/api/productlist', //api
dataType: 'json'
})
TypeScript中的泛型
软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
function add<T>(a: T, b: T): void {
console.log(a)
console.log(b)
}
add(1, 2)
add('a', 'b')
接口 + 泛型 -> 函数类型
let addFn: AddFn<boolean> = function (a, b) {
console.log(a)
console.log(b)
return [{ id: 1, name: '浙大正呈' }]
}
interface arrIF {
readonly id: number,
name: string
}
interface AddFn<T> {
(a: T, b: number): arrIF[]
}
console.log(addFn(false, 2))