学习TS的原因?
一方面,顺应潮流,最近校招面试的大公司都陆续问道是否会 TypeScript,由于之前在滴滴都是使用的 React+JS,所以现在决定把 TS 学了,以备不时之需。另一方面,前端程序员确实比较缺乏面向对象的思想,继承、泛型、强类型之类的,就大学学了,用得寥寥,乘此机会也加强一下自己这方面的基础。
安装 typescripts 与自动编译 TS 文件
cnpm install typescript -g
配置好环境变量后重新打开终端看 ts 是否安装成功,版本号出来代表已成功
编译 .ts 文件报错:
tsc : 无法加载文件 E:\小伍的文件\node安装文件夹\node_global\tsc.ps1
解决:
查看 shell 中的执行策略:
get-ExecutionPolicy
我的是:Restricted
更新执行策略:
Set-ExecutionPolicy -Scope CurrentUser
手动输入:RemoteSigned 即可
再执行 tsc index.ts 就能将 ts 文件成功编译成 es5 的 index.js,这样浏览器才会认识
但是目前每次编写 ts 代码都需要手动编译成 es5 代码比较麻烦,所以我们来开启 tsc 监视
生成 tsconfig.json 配置文件
修改配置文件中的输出文件夹:
每次当我们修改 ts 代码都能实时编译出 es5 代码了,index.js 文件就放在名为 js 文件夹下
TS 中的数据类型
作用:使代码更规范、更有利于维护
Number、String、Boolean、Null、undefined、Array、tuple(元组类型)、enum(枚举类型)、any(任意类型)、void 类型、never(其它类型)
let str: string = '你好 ts'
let num: number = 123
let a: any = 789
str = 123 // 报错,因为 str 是字符串类型,不能被赋值为数字类型
let arr: number[] = [1, 2, 3, 4, 5] // 定义一个只能放数字的数组
let arr1:Array<string> = ['a', 'b', 'c'] // 数组指定一种类型
// 元组类型,可以指定数组的多种类型
let arr:[string, boolean, number] = ['abs', true, 145]
// 枚举类型
enum Flag {
success = 1, error = 0 }
let isF:Flag = Flag.success // 1
enum frult {
apple, pear, banana}
let f:frult = frult.apple // 1 不赋值就是索引的值
// any(任意类型)的用处:当获取 DOM 节点时,我们或许会改变 DOM 节点的样式,这时我们 .style 时就会报错,就需要赋值any类型
let oBox:any = document.getElementById('box') // 这里不指定any就会报错
oBox.style.color = 'red'
// null、undefined是其它类型(never)的子类型
let num:undefined // 打印输出undefined,正确,否则定义不赋值就会报错
let num:number | undefined
TS中的方法(函数)
// void 标识这个方法没有返回值
function go(): void {
console.log('no back')
}
// 函数返回数字
function sum(): number{
return 123
}
// 匿名函数
let fun = function ():number{
return 456
}
// 规定参数类型
function getInfo(name: string, age: number): string {
return `${
name}---${
age}`
}
getInfo('李四', 18) // 李四---18
方法的可选参数
上面的函数,必须要传两个参数,如果我们只想传 name 不想传 age,就在 age 处打 ?
function getInfo(name: string, age?: number): string {
...
}
方法的默认参数
ES5 中无法设置默认参数,ES6 和 TS 皆可设置,设置了默认参数后,参数可传也可不传
function getInfo(name?: string, age: number = 20): string {
return `${
name}---${
age}`
}
getInfo('李四') // 李四---20
方法的剩余参数
function getSum(...res: number[]): number {
let sum = 0
res.map(item => sum += item)
return sum
}
getSum(1, 2, 3, 4) // 10
ts函数重载
定义多个重名方法,前面的方法都没有方法体,最后一个方法参数 any,返回值 any 内部做判断返回具体的值
function getSomething(name:string):string;
function getSomething(name:number):string;
function getSomething(name:any):any{
if(typeof name == 'string'){
return `我是${
name}`
}
else{
return `年龄透明——${
name}`
}
}
getSomething('张三'); // 我是张三
getSomething(true); // 编译不通过
// 也可以传两个参数
function getSomething(name:string):string;
function getSomething(name:string, age:number):string;
function getSomething(name:any, age?:any):any{
// 最后一个函数都是 any
if(age){
return `我是${
name}年龄${
age}`
}
else{
return `我是${
name}`
}
};
TS 中的类
class Person {
name: string;
constructor(name: string) {
this.name = name
}
getName(): string {
return `${
this.name}`
}
setName(name: string): void {
this.name = name
}
}
let p = new Person('张三')
p.getName()// 张三
p.setName('王五')
p.getName(); // 王五
TS 中实现继承
class Animal {
name: string;
constructor(name: string) {
this.name = name
}
cry(): string {
return `${
this.name}会咆哮`
}
}
let a = new Animal('tiger')
console.log(a.cry()); // tiger 会咆哮
class Loin extends Animal{
constructor(name:string){
super(name)
}
king():string{
return `${
this.name}是丛林之王`
}
}
let l = new Loin('狮子')
console.log(l.king()); // 狮子是丛林之王
console.log(l.cry()); // 狮子会咆哮
TS 类中的三种修饰符
TS 中的静态方法
静态方法无法使用类中的非静态属性
class Animal {
name: string;
static age:number = 5 // 静态属性
constructor(name: string) {
this.name = name
}
cry(): string {
return `${
this.name}会咆哮`
}
static getAge():number{
// 静态方法
// return ${this.name} 报错,因为静态方法无法使用类中的非静态属性
return Animal.age
}
}
TS 中的多态
1、父类定义一个方法,但不去实现
2、让继承它的子类去实现,每一个子类有不同的实现,但不是一定要去实现,换个意思说,也可以不去实现;
3、多态属于继承
class Animal {
name: string;
constructor(name: string) {
this.name = name
}
cry(): string {
return `${
this.name}会咆哮`
}
eat(){
// 父类定义了方法但不实现,因为它的子类很多,父类也不知道要具体实现什么
}
}
class Loin extends Animal{
constructor(name:string){
super(name)
}
king():string{
return `${
this.name}是丛林之王`
}
eat():string{
// 子类实现父类的方法,各个子类中有不同的实现方式
return `${
this.name}吃肉`
}
}
class Cow extends Animal{
constructor(name:string){
super(name)
}
eat():string{
// 子类实现父类的方法,各个子类中有不同的实现方式
return `${
this.name}吃青草`
}
}
let l = new Loin('狮子')
let c = new Cow('牛')
console.log(l.eat()); // 狮子吃肉
console.log(c.eat()); // 牛吃青草
用 abstract 修饰的抽象类和抽象方法
规定:
1、抽象类(用 abstract 修饰的类)中的抽象方法不包含具体的实现,并且必须在派生类中实现。
2、抽象方法必须包含在抽象类中,要求派生类中必须包含这个方法(不然报错),当然抽象类中还可以写其它的普通属性和方法
3、抽象类提供其它类继承的基类,它自身不能被实例化。
abstract class Animal {
name: string; // 抽象类中的正常属性
constructor(name: string) {
this.name = name
}
cry(): string {
// 抽象类中的正常方法
return `${
this.name}会咆哮`
}
abstract eat():any // 抽象类中类定义了抽象方法,但不能有方法体
}
let a = new Animal() // 报错,因为无法创建抽象类的实例
class Loin extends Animal{
// 子类继承抽象父类,必须实现父类中的抽象方法
constructor(name:string){
super(name)
}
king():string{
return `${
this.name}是丛林之王`
}
eat():string{
// 子类继承抽象父类,必须实现父类中的抽象方法
return `${
this.name}吃肉`
}
}
class Cow extends Animal{
constructor(name:string){
super(name)
}
eat():string{
// 子类继承抽象父类,必须实现父类中的抽象方法
return `${
this.name}吃青草`
}
}
let l = new Loin('狮子')
let c = new Cow('牛')
console.log(l.eat()); // 狮子吃肉
console.log(c.eat()); // 牛吃青草
TS 中的接口 interface
接口作用:接口起到限制和规范的作用
属性接口(用得较多)
对 json 的约束,也就是对方法的传入参数进行约束
interface Person{
// 属性接口定义了必须传入 name、age 的规范,sex 属于可选参数,可传可不传
name: string;
age: number;
sex?: string;
}
function getInfo(info:Person){
return `${
info.name}--${
info.age}`
}
console.log(getInfo({
name:'张三',age:22})); // 传入函数的参数必须包含 name、age,少传其中一个就会报错
函数类型接口
实现接口的函数,必须满足接口的格式:两个参数必须有,并且类型一致,函数返回值必须是 string 类型
interface getInfoFn {
(value: string, value2: number): string }
let getInfo: getInfoFn = function (name: string, age: number): string {
return `我叫${
name},今年${
age}岁`
}
getInfo('张三', 18); // 我叫张三,今年 18 岁
getInfo(123, 18); // 报错,123 不是 string 类型
类类型接口
对类的约束,和抽象类类似,父类接口中规定的属性和接口,在子类中必须要有
interface Person{
// 类接口规定了子类必须要有 name 属性和 say 方法
name: string;
say(): void;
}
class Chind implements Person{
// 用 implements 表示 Child 类要实现 Person 这个接口
name: string;
constructor(name: string) {
this.name = name
}
say() {
console.log(`${
this.name}很漂亮`);
}
}
let c = new Chind('小白')
console.log(c.say()); // 小白很漂亮
接口扩展:接口可以继承接口
interface Person{
// 类接口规定了子类必须要有 name 属性和 say 方法
name: string;
say(): void;
}
interface YellowPerson extends Person{
// YellowPerson 类接口继承了 Person 接口,规定了子类必须要有 name 属性,say、eat 方法
eat():string;
}
class Chind implements YellowPerson{
// 用 implements 表示 Child 类要实现 YellowPerson 这个接口
name:string;
constructor(name:string){
this.name = name
}
say(){
console.log(`${
this.name}很漂亮`);
}
eat(){
return `${
this.name}爱吃肉`;
}
}
let c = new Chind('小黄')
console.log(c.say()); // 小黄很漂亮
console.log(c.eat()); // 小黄爱吃肉
TS 中的泛型
可以使用泛型来创建可重用的组件,这样一个组件可以支持多种数据类型
如果我们要求一个函数可以返回 string 类型、同时也可以返回 number 类型,那么用 any 可以解决这个问题:
function getInfo(value: any): any {
return value
}
getInfo('张三'); // 张三
getInfo(18); // 18
以上写法缺点:any 相当于放弃了类型检查,也就是我传入 number 类型,但是它仍然可以返回 string 数据类型
泛型函数
可以支持不特定的数据类型,函数具体返回什么数据类型由传入的参数决定,并且它们始终能保持一致,对不特定数据类型支持
要求:传入的参数和返回的参数一致
function getInfo<T>(value:T): T {
// 泛型定义
return value
}
getInfo<string>('张三'); // 泛型函数
getInfo<string>(18); // 报错,因为传入的参数数据类型必须和泛型一致
getInfo<number>(18); // 正确
泛型类
class getMin<T>{
// 定义泛型类
public arr: T[] = [] // 而后的数据类型都和 T 一致
push(value: T): void {
this.arr.push(value)
}
min(): T {
let num = this.arr[0]
this.arr.forEach(item => {
if (item < num) {
num = item
}
})
return num
}
}
let g = new getMin<number>() // 实例化类,并指定T代表的数据类型为 number
g.push(78)
g.push(9)
g.push(3)
g.push(4)
console.log(g.min()); // 3
let s = new getMin<string>() // 实例化类,并指定T代表的数据类型为 string
s.push('s')
s.push('c')
s.push('h')
s.push('e')
console.log(s.min()); // c
泛型接口
第一种方式:
interface getInfoFn {
<T>(value: T, value2: T): T }
let getInfo: getInfoFn = function<T> (name: T, age: T): any {
return `我叫${
name},今年${
age}岁`
}
console.log(getInfo<string>('张三', '18')); // 我叫张三,今年 18 岁
第二种方式:
interface getInfoFn<T> {
(value: T, value2: T): T }
function getInfo<T> (name: T, age: T): any {
return `我叫${
name},今年${
age}岁`
}
let myGetInfo : getInfoFn<string> = getInfo
console.log(myGetInfo('张三', '18')); // 我叫张三,今年 18 岁
工作用一般使用泛型格式:只关注于我需要的数据,不关心数据的具体格式,接口是什么格式数据就是什么格式,别人可以任意定义接口,传入自己需要的数据格式
持续更新中…