一、Typescript简介
TypeScript是JavaScript类型的超集,可以简单理解为是JavaScript的“进化版”,它可以编译成纯JavaScript。
TypeScript 和 JavaScript 不同之处在于,它可以在编写代码的时候,就对一些错误进行提示,还能在使用某个数据的时候,为你列出这个数据可以访问的属性和方法。
TypeScript的开发体验远远超过以往纯JavaScript的开发体验,无需运行程序即可修复潜在bug。
TypeScript可以构建大型程序,并在任何浏览器、任何计算机和任何操作系统上运行,且是开源的。
1、特点
-
类型检查,语法提示
在TS中允许为变量指定类型。当你给已定义类型的变量赋值的时候,值的类型不对,便会有错误提示 -
约束类型,减少不必要的代码逻辑
-
代码重构
Typescript 支持类型最大的好处是可读性。 类型可以给开发者更多的信息,是最有价值的文档。类型很好的体现了代码即文档这一思想。
2、开发条件
- 大型项目,代码量较多
- 开源项目,尤其是工具函数或组件库
- 进行封装,组件化开发的时候(有一定开发基础)
3、支持TypeScript的编辑器
TypeScript 最大的优势之一便是增强了编辑器和 IDE 的功能,包括代码补全、接口提示、跳转到定义、重构等。 主流的编辑器都支持 TypeScript,推荐使用 Visual Studio Code
,除此外还有:
- Sublime Text
- Atom
- WebStorm
- Vim
- Emacs
- Eclipse
- Visual Studio 2015
- Visual Studio 2013
- IDEA 系列
二、基础类型
1、字符串 string
// 普通字符串
let name = ref<string>("jeasu");
// 带变量的字符串
let msg = ref<string>(`my name is ${name.value}`);
// 拼接的字符串
let sentence = ref<string>("Hello, my name is " + name + ".\n\n" + "I'll be " + (age + 1) + " years old next month.");
2、布尔类型 boolean
// 布尔类型
let isAdult = ref<boolean>(false);
3、数字 number
// 数值类型
let age = ref<number>(17);
4、任意值 any
如果是一个普通类型,在赋值过程中改变类型是不被允许的,任意值(Any)用来表示允许赋值为任意类型。
let a1 = ref<string>('seven');
a1 = 7;//error
//但如果是 any 类型,则允许被赋值为任意类型。
let sim = ref<any>("seven");
sim = 7;
console.log(sim, "任意类型"); //7
//变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型
let a3;
a3 = 'seven';
a3 = 7;
//相当于
let a3:any;
a3 = 'seven';
a3 = 7;
//any定义多元数组
let arr = reactive<any[]>([1, true, "hellow"]);
arr[1] = 100;
console.log(arr, "any定义多元数组"); // [1,100,'hellow']
5、数组 array
两种方式定义数组:
- 元素类型后面加上
[ ]
let list1 = ref<number[]>([1, 2, 3]);
let list2 = ref<string[]>(["1", "2", "a"]);
let list3 = ref<any[]>([1,true,'abc',{id:2}])
- 使用数组泛型,
Array<元素类型>
let hobbies = ref<Array<string>>(["历史", "地理", "生物"]);
let list4 = ref<Array<number | string>>(['dasahk',10])
6、联合类型
一个变量定义可能的多种类型
// 联合类型
let collection1 = ref<number | string | boolean>(6);
let collection2 = ref<number | string | boolean>("good");
let collection3 = ref<number | string | boolean>(true);
// 联合类型数组
let collection4 = ref<(number | string | boolean)[]>(["hasd", true, 36]);
//联合类型的注意点:对联合类型的数据的操作
// const fn = (str: string | number): number => {
// return str.length; //红波浪线报错,原因是number 没有length属性
// };
// fn("hello");
7、元组类型 Tuple
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同,且数组各项类型与定义一一对应。元组的长度和类型不可变。
let arr1 = ref<[number, string]>([8, "haode"]);
8、Null 和Undefined
默认情况下Null 和Undefined是所有类型的子类型。
可以把 Null 和Undefined赋值给number类型的变量.
// undefind类型
let u: undefined = undefined;
// null类型
let n: null = null;
9、Never 类型
表示永远不会存在值的类型,常用来定义抛出异常或根本就不会有返回值的函数
never 类型是任何类型的子类型,也可以赋值给任何类型;没有类型是never的子类型或者可以赋值给类型(除了never本身)
never 和 void 之间的区别是void 意味着至少要返回一个 undefined 或者 null ,而 never 意味着不会正常执行到 函数的终点。
// 返回never的函数必须存在无法达到的终点
const error = (message: string): never => {
throw new Error(message);
};
const infiniteLoop = (): never=> {
while (true) { }
}
// 推断的返回值类型为never
const fail = ()=> {
return error("Something failed");
}
10、Object 类型
object 表示非原始类型,也就是除number,string,boolean,symbol,null或者undefined之外的类型,使用object类型,就可以更好的表示想Object.create这样的API
const create = (o: object | null): void => {};
create({ prop: 0 }); // OK
create(null); // OK
// create(42); // Error
// create("string"); // Error
// create(false); // Error
// create(undefined); // Error
11、类型断言
类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。
方式有两种:
一是尖括号
语法:
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
另一个为as
语法:
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
12、类型别名 type
type abc = string | number[];
type n = number;
let n1: abc;
n1 = "4";
n1 = [1];
const fn1 = (str: abc): n => {
return str.length;
};
fn1("a");
三、接口 Interfaces
接口可以用于对 “对象的形状(Shape)” 进行描述。
注意:顺序可以乱,但是定义的对象要受到接口的约束。
//定义对象属性
interface defineObj {
readonly name: string; //只读属性
age: number; //必填
sex?: string; //选填
call(): string; //有返回值,类型为string
action(): void; //必填、无返回值
[propName: string]: any; //任意属性
//需要注意的是,一旦定义了任意属性,那么确定属性和可选属性都必须是它的子属性
}
const obj = reactive<defineObj>({
name: "abc", //只读,不可修改
age: 18, //sex:'男',
a: "a",
b: 9,
c: true,
call: () => "call",
action: function () {
console.log("void接口");
},
o: { id: 1 },
});
console.log(obj.call()); //call
obj.action(); //void接口
return {
obj,
};
接口在函数中的应用:
- 当传入的参数是对象类型的时候:
//不用接口定义时
const fn = (obj: { id: number; name: string }): string => {
return obj.name;
};
const oh = reactive({ id: 3, name: "jeasu" });
console.log(fn(oh)); //jeasu
//仅对函数入参做约束
interface params {
id: number;
name: string;
age: number;
}
const fn1 = (o: params): number => {
return o.age;
};
const oh1 = reactive({ id: 3, name: "jeasu", age: 18 });
console.log(fn1(oh1)); //18
//对整个函数的类型检查,建议对返回值类型也要定义
iinterface SearchFun {
(a: string, b: string): boolean;
}
const fn2: SearchFun = (s1, s2) => {
let i = s1.search(s2);
return i !== -1;
// return s1;
};
console.log(fn2("dsdahjk", "jk")); // true
- 接口继承接口
// 二维坐标接口
interface TwoDPoint{
x: number,
y: number
}
//三维坐标中的z坐标接口
interface ThreeDPoint{
z: number
}
//四维坐标接口继承了二维坐标接口的x,y坐标和三维接口的z坐标
interface FourDPoint extends ThreeDPoint, TwoDPoint{
//内还定义了四维坐标独有的时间坐标
time: Date
}
//实例化四维坐标接口
const poi2 = reactive<FourDPoint>({
z: 100,
x: 200,
y: 300,
time: new Date(),
});
console.log(poi2, "poi2");//Proxy对象{{z: 100,x: 200,y: 300,time: Mon Oct 11 2021 15:29:15 GMT+0800 (中国标准时间)}}
四、泛型 Generics
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
语法格式: <变量类型>
当定义相同结构的函数,但是传入参数类型不同时,若是根据类型依次写容易造成代码冗余,比如:
function fnn(x:number,y:number):number[]{
return [x,y]
}
function fnn(x:string,y:string):string[]{
return [x,y]
}
function fnn(x:boolean,y:boolean):boolean[]{
return [x,y]
}
这时可以将类型用一个变量替代,在使用的时候,将变量类型传过去。
1、泛型变量
泛型变量 T ,T 表示任何类型,也可以用其他字母代替
因此上面的代码可以写成:
//函数声明方式
function declaration<T>(x: T, y: T): T[] {
return [x, y];
}
//函数表达式方式
const expression = <T>(n1: T, n2: T): T[] => {
return [n1, n2];
};
// 单类型
console.log(declaration<string>("1", "2")); //['1','2']
console.log(expression<boolean>(true, false)); //[true,false]
console.log(expression<number>(6, 7)); //[6,7]
//联合类型
console.log(expression<number | string>(6, "a")); //[6,"a"]
//当我们不给传类型的时候,类型推断编译器会自动的帮我们判断传入的是什么类型,此时传入的数据只能为单一类型
console.log(expression(1, 23)); //[1,23]
//泛型的约束
//错误示范 求变量的长度
// let variable1 = <T>(str: T): number => {
// return str.length; // T是变量,未指定类型,未必会有length这个属性
// };
//修改:
// 给参数限制类型
let limit1 = <T>(str: string | T[]): number => {
return str.length;
};
console.log(limit1<number>([1, 3])); //2
//或者给泛型变量添加约束 extends
let limit2 = <T extends String>(arr: T): number => {
return arr.length;
};
console.log(limit2<string>("one")); //3
//泛型的接口约束:就是让这个变量必须有length属性,此时就限定了变量类型有length属性,只能为string ,或者数组类型
interface ILengthNum {
length: number;
}
const limit3 = <T extends ILengthNum>(str: T): number => {
return str.length;
};
console.log(limit3<string>("oneworld")); //8
console.log(limit3<string[]>(["dasjd", "dhksah", "dahskdha"])); //3
console.log(limit3<number[]>([12, 456, 79, 465])); //4
//多个类型参数
const multi = <N, S>(sum: [N, S]): [S, N] => {
return [sum[1], sum[0]]; //实现的是交换数组内两项的位置
};
console.log(multi<number, string>([1, "one"])); //["one",1]
2、泛型接口
//泛型接口
interface genface1 {
<T>(a: T, b: T): boolean;
}
const func1: genface1 = (x, y) => {
return x == y;
};
console.log(func1<number>(1111, 5)); //false
console.log(func1<string>("abc", "abc")); //true
//另一种 把泛型参数提前放在接口名上
interface genface2<T> {
(a: T, b: T): boolean;
}
const func2: genface2<number> = (x, y) => {
return x == y;
};
console.log(func2(7, 5)); //false
//另一种写法,先定义类型,再赋值函数
let func3: genface2<string>;
func3 = (x, y) => {
return x == y;
};
console.log(func3("abc", "abc")); //true
//多类型泛型接口
interface createA3<N, T> {
(a: N, b: T): Array<T>;
}
let func4: createA3<number, string>;
func4 = function (i, s) {
let arr: string[] = [];
arr[i] = s;
return arr;
};
func4(1, "dqwy");
//泛型约束
interface Length4 {
length: number;
}
interface createA4<N, T extends Length4> {
(a: N, b: T): string;
}
let func5: createA4<number, string>;
func5 = function (i, s) {
//var arr:string[] = []
//return arr
return s;
};
func5(2, "dqwy");
3、用泛型变量和any的区别
//使用泛型变量 T
function fun6<T>(x: T, y: T): T[] {
return [x, y];
}
fun6<string>("a", "b");
//使用 any:缺点就是传入的类型和返回的类型不确定
function fun7(x: any, y: any): any[] {
return [x, y];
}
fun7("a", "b");
五、类 class
类是属性和函数的集合,是生成对象(Object)或类实例的模板。
1、类的定义和使用
//类的定义和使用
class MyInfo { //class是关键字,类名默认全部大写首字母
name: string; //属性
weather: string; //属性
constructor(name: string, weather: string){ //构造函数,一般用于初始化。如果没有,TS会自动生成一个,以备用new创建类实例时调用。
this.name = name;
this.weather = weather;
}
printInfo(): void { //其它函数,无返回值
console.log(`Hello, ${this.name}.`);
console.log(`Today is ${this.weather}.`);
}
}
let myData = new MyInfo('QiGe', 'raining'); //使用new关键字生成对象,即该类的实例
myData.printInfo();
2、类的属性和函数的访问权限
类中的属性和函数都有访问权限,默认为public即全局可访问,其次为protected即可在类的内部和其子类的内部可访问,最后为private,只能在该类内部可访问。
//访问权限
class MyInfo { //class是关键字,类名默认全部大写首字母
public name: string; //public属性,可省略
private _weather: string; //私有属性,习惯以_开头进行命名
constructor(name: string, weather: string){ //构造函数,一般用于初始化
this.name = name;
this._weather = weather;
}
printInfo(): void { //其它函数
this._test();
console.log(`Hello, ${this.name}.`);
console.log(`Today is ${this._weather}.`);
}
private _test(): void {
console.log('You can not call me outside!');
}
}
let myData = new MyInfo('QiGe', 'raining'); //使用new关键字生成对象
console.log(myData._weather); //error!
myData._test(); //error
myData.printInfo();
3、存取器getter、setter
当在类外部时,建议设置getter和setter操作其private属性,即使public属性也如此。
//getter和setter
class MyInfo { //class是关键字,类名默认全部大写首字母
private readonly _name: string; //私有属性,外部不可访问。readonly使其只能在初始化时赋值,以后不可更改。
private _weather: string; //私有属性,习惯以_开头进行命名
constructor(name: string, weather: string){ //构造函数,一般用于初始化
this._name = name;
this._weather = weather;
}
get name(): string {
return this._name;
}
set name(value: string) { //error! _name有readonly属性
this._name = value;
}
get weather(): string {
return this._weather;
}
set weather(value: string) {
this._weather = value;
}
}
let myData = new MyInfo('QiGe', 'raining'); //使用new关键字生成对象
console.log(myData.name, myData.weather);
myData.weather = 'sunny'; //OK
myData.name = 'Wang'; //error!
console.log(myData);
4、静态属性
类中的属性或函数有static修饰,则可直接使用而不需要实例化
//静态属性,内建或自定义,无需new即可使用
console.log(Math.round(89.64)); //90
console.log(Math.pow(2, 8)); //256
class MyStaticClass {
static place = 'Earth';
static printInfo() {
console.log('We have only one Earth!');
}
}
console.log(MyStaticClass.place);
MyStaticClass.printInfo();
5、继承
可以通过extends关键字继承其它类,从而成为其子类
class Animal {
// 当构造函数传入的参数加上了“访问权限控制符”,则同时会声明同名类属性,并赋值
constructor(public name: string) { }
protected log(message: string) {
console.log(message);
}
move(distanceInMeters: number = 0) {
this.log(`${this.name} moved ${distanceInMeters}m.`);//请注意name来自何处
this.log('==============');
}
}
class Horse extends Animal {
constructor(name: string) {
super(name); // 通过super调用父类构造器
}
run(distanceInMeters = 50) { //自己独有的函数
this.log("Clop, clop...");
super.move(distanceInMeters); // 通过super调用父类方法
}
}
class Eagle extends Animal {
constructor(name: string) { super(name); }
reborn() { //自己独有的函数
console.log('Reborn? It is a joke, hahaha!');
}
}
let tom: Horse = new Horse("Tommy the Palomino");
tom.run(8964);
let sam: Eagle = new Eagle("Sammy the Hawk");
sam.move(1024);//sam的move函数来自何处?
sam.reborn();
6、类实现接口 (implements)
interface ClassType {
name: string;
action(): void;
}
class Obj implements ClassType {
//类Obj受到接口ClassType的约束
// 类中的属性和方法要受到接口的约束,但是添加属性或方法不会报错
//属性和方法只能比接口里定义的多,不能少
name: string;
age: number; //新增属性,接口里未定义
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
action() {
console.log("执行");
}
a() {
return "sah";
} //新增方法,接口未定义
}
const obj1 = new Obj("hahaha", 18);
console.log(obj1.name); //hahaha
console.log(obj1.age); //18
console.log(obj1.a()); //sah
obj1.action(); //执行
7、泛型在类中的运用
class A2<T> {
n: T;
constructor(num: T) {
this.n = num;
}
add(x: T): T {
return x;
}
}
var a2 = new A2<number>(1);
console.log(a2.n); //1
console.log(a2.add(3)); //3
六、模块Module
对于大型的项目,需要使用模块进行管理。每个 .ts 文件就是一个模块,通过 export 来对外部模块暴露元素,通过 import 来引入模块。
小结
关于Typescript的基础学习大概就到这里啦,前端的学习路还远呢,加油吧!