目录
ts和js区别
阮一峰ts官网http://ts.xcatliu.com/introduction/what-is-typescript.html
ts官网https://www.tslang.cn/docs/home.html
ts就到vue3或者是react中去使用吧,在vue2中 使用ts基本上是给自己添加烦恼.
typescript简介
typescript 是javascript类型的超集, 可以编译成纯javascript, ,可以跨浏览器,跨平台运行
如果你很在乎开发速度,快速迭代,那么建议你继续用 JavaScript。如果你需要高稳定性,那么建议你使用 TypeScript。
编译TS
手动编译:tsc home.ts (执行命令就可以将ts代码编译成另一个同名js文件)
运行:node home.js
自动编译
1). 生成配置文件tsconfig.json
tsc --init
2). 修改tsconfig.json配置
"outDir": "./js",
"strict": false,//关闭严格模式
3). 启动监视任务:
终端 -> 运行任务 -> 监视tsconfig.json
在tsconfig.json同级文件下新建index.html和index.ts
执行上述 3 步骤会生成同名index.js文件
在index.html中引入idnex.js文件并浏览器运行
完成自动编译:之后修改ts文件可直接输出
数据类型
js基本数据类型:number string boolean undefined null symbol
js的引用数据类型: Object array function
let 变量名:数据类型 = 值
// 数据类型定义
let num:number = 100;
let str:string = 'hello'
let flag:boolean = true;
let timer:undefined = undefined;
let obj:null = null;
默认情况下null和undefined是所有类型的子类型 ; 就是说你可以把 null和undefined赋值给任何类型的变量
数组
let 变量名:[number, string] = [1, '3'];
let 变量名:[number | string, string] = [1, '3'];
let str:[number | string, string] = [1, "3"];
console.log(str);//[1, "3"];
数组的类型
- 「类型 + 方括号」表示法
let str1:number[]=[1,3,4,5];
- 元组: 限定数组中每个元素的类型 , 同时也限定了数组中元素的个数;
(元组不是数组,只是刚好符合数组的规范)
let arr2:[number, string] = [1, 'hello']
- 通过 数组的泛型 Array<数据类型>
let arr3:Array<number> = [21,4,45,23]
let arr4:Array<string> = ['ajds']
- 通过接口定义数组 [index:number] 限定的是数组索引值的类型, 后边是限定数组中元素的类型
interface a{
// [index:number]:number
[index:number]:string
}
// let arr5:a = [1,4,5,6]
let arr5:a = ['234', '345', '35432']
允许数组中出现任意值:any[]
let arr6:any[] = [23, '345', {name:'zadf'}]
枚举
定于枚举类型: 使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript支持数字的和基于字符串的枚举。
// 通过关键字 enum 定义枚举类型
// 一旦定义枚举类型 : 数字类型的枚举,会存在默认值,默认值从0开始 ; 默认值会依次递增
enum Per{
// 随意自定义 ; 自定义的是常量的名字,而没有给常量赋值,常量的默认是从0开始的
A,//默认A=0
b,//B=1
}
/*Per[1] 这是通过枚举类型的值 获取枚举类型的名字
[]中的数字是 枚举类型的值; 而不是索引值 */
console.log(Per[1]);//b
// 可以手动指定枚举类型的值
enum E {
a, // 第一个枚举名没有指定值,则默认是从0开始
b,
d = 12, // 指定了值,则使用指定的值
c, // 没有指定值,会从上一个枚举值自动递增
f = 16
}
//console.log(E[0], E[12],E[13]);//a b c
//console.log(E.a, E.b, E.c);//0 1 13
console.log(E['a'], E['b'], E.c);//0 1 13
// 访问枚举类型有两种方式:
// 第一种是通过枚举的值访问枚举类型名 枚举变量名[值];
// 第二种是通过枚举类型名访问枚举类型值 枚举变量名.枚举常量名 或者 枚举变量名['枚举常量名'] ;
任意类型any
使用 any 关键字 定义任意类型
let 变量名:any = value;
// 后期可以随便进行更改
例子 let str:any=100;
console.log(str);//100
2.1. 空值
代表该函数没有任何返回值
function alertName(): void {
alert('My name is Tom');
}
声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null
类型推断
TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推断
/* 定义变量时赋值了, 推断为对应的类型 */
let x = 100; // 这种情况下 ts 就会使用类型推断
x= 'abc' // error
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:
- 隐式任意类型
let str; // 通过类型推论 推断为 任意类型
str = 'hello';
str = 100;
联合类型
联合类型(Union Types)表示取值可以为多种类型中的一种。 使用 | 符号
let 变量名: 数据类型1 | 数据类型2 | ... = value ;// value 可以是 类型的任意一个; 后期可以更改为定义的类型中任意一个
let arr:number|string=100
arr="hello"
console.log(arr);//hello
类型断言
(Type Assertion)可以用来手动指定一个值的类型
语法:
方式1 <类型>值
方式2 值 as 类型 tsx中只能用这种方式
// 接口约束 类型断言
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
// 类型断言 : 我知道自己在干嘛,所以不需要ts帮我们进行类型判断
function getName(animal: Cat | Fish) {
//return animal.name;//HEISE
// animal.run();报错
//animal.swim();报错
// 类型断言 (我知道自己要去用Fish接口)
(animal as Fish).swim()
}
let str = getName({
name: 'HEISE', swim: function () {
console.log("函数")
}
})
console.log(str);//函数
接口
在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。
接口是对象的状态(属性)和行为(方法)的抽象(描述)
//定义了一个接口 Person,接口名字首字母大写
interface Person {
name: string;
age: number;
}
//接着定义了一个变量 tom,它的类型是 Person
let tom: Person = {
name: 'Tom',
age: 25
};
console.log(tom.name);//Tom
- 可选属性
定义可选属性 通过 ? : 如果有一些属性是可有可无,可以定义为可选属性,通过 在属性名后边添加 ? 实现
interface Person{
name:string;
age:number;
sex?:string;
hariColor?:string
}
let stu:Person = {
name:'hello',
age:18,
sex:'nv',
hariColor:'123984'
}
console.log(stu);
- 定义任意属性(也是可有可无)
[propName:数据类型] :any 使用 [propName: string] 定义了任意属性取 string 类型的值。
interface Person {
name: string;
age?: number;//可选属性
[propName: string]: any;//任意类型
}
let tom: Person = {
name: 'Tom',
gender: 'male'//可以是任意类型
};
- 定义只读属性
通过 readonly 关键字
只读: 只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候:
interface Person {
readonly name:string;
age:number
}
//只读 在该环节生效的
let stu1:Person = {
name:'张三',
age:19
}
//console.log(stu.name);
stu1.name="李四"//只读属性不允许修改
函数返回值 ,返回值是对象
interface Person {
name: string;
age: number
}
//函数返回值 ,返回值是对象
function Stu1(a:Person):{name:string, age:number}{
let newStu = {name:'张三', age:100}
if(a.name){
newStu.name = a.name;
}
if(a.age){
newStu.age = a.age;
}
return newStu
}
var res = Stu1({name:'李四', age:18})
console.log(res);//{name:'李四', age:18}
函数
基本示例
函数声明式
function fun(a:number, b:number){ }
函数类型
函数表达式
let fun2 = function(a:number, b:number){ }
- 函数返回值的定义
// 限定返回值
function fun(a:number):number{//第二个number就是限定的返回值(也叫输出)
return a + 100;
}
console.log(fun(100));//200
// 函数存在返回值
function 函数名(参数名:参数类型):返回值的类型{
return value; // value 比如符合你定义的类型
}
function sum(a:number,b:number):number{
return a+b;
}
console.log(sum(1,2));//3
// 函数没有返回值 使用void 关键字
function 函数名(参数名:参数类型):viod{
// 函数不需要返回值
}
function red(a,d):void{
console.log(a);
}
red(100,200)
// 如果函数的返回值是一个对象
function f(n:string, a:number):{name:string,age:number}{
return {name:n, age:a}
}
function f(n:string, a:number):{name:string,age:number}{
return {name:n, age:a}
}
console.log(f("hello",100));//{ name: 'hello', age: 100 }
书写函数的完整格式
// 针对的是函数表达式的方式
// 定义函数时的变量名可以不同,但是数据类型必须一致
// 区分 => 和 es6 的箭头函数 箭头 number这里是规定输出值的类型,不是箭头函数
let fun2:(x:number, y:number) => number = function(a:number, b:number):number{
return a + b;
}
console.log(fun2(10,20));//30
(x:number,y:number)=>number当前的这个函数的类型
function(X:number,y:number):number{return x+y}就相当于符合上面的这个函数类型的值
箭头如果实在函数表达式中(就是声明函数时使用的),意味着是指定函数返回值的类型,除此以外代表都是箭头函数
let fun2:(x:number, y:number) => string = (a:number, b:number):string=>{//第二个箭头才是箭头函数
return (a + b).toString();
}
console.log(fun2(10,20));//30
可选参数和默认参数
- 可选参数
function f(x?:number){}
- 默认值参数
function f(x:number, y = 100 ){}
- 剩余参数 我们可能不确定参数个数: 这个时候可以使用剩余参数 (rest参数)
function fun(a:number, b:number, ...args:number[]){
console.log(a, b, args); //1 3 [ 4, 6, 8 ]
}
fun(1, 3, 4, 6, 8)
函数重载
函数重载: 函数名相同, 而形参不同的多个函数
函数重载或者方法重载适用于某个功能一样,但是细节有不一样
比如说一个函数有多个参数,每一个参数代表不同意义 这个时候就可使用函数重载
// 定义函数重载类型
function 函数名(参数:数据类型1):返回值类型1;
function 函数名(参数:数据类型2):返回值类型2;
// 函数的实现的定义
function 函数名(参数:数据类型1 | 数据类型2):数据类型1 | 数据类型2{
// 逻辑处理
}
function 函数名(参数:any):any{
// 逻辑处理
}
具体实现:
/* 1 定义重载函数的类型 : 定义的是函数类型 */
function add(x: string, y: string): string
function add(x: number, y: number): number
/* 2 定义函数 函数类型定义和函数定义之间不能 存在任何代码 */
function add(x: string | number, y: string | number): string | number {
if (typeof x === 'string' && typeof y === 'string') {
return x + y
} else if (typeof x === 'number' && typeof y === 'number') {
return x + y
}
}
console.log(add("1", "2"));//12
console.log(add(1, 2));//3
console.log(add("1", 2));//undefined
//上述例子add("1", 2)(在没有定义重载函数的类型前):字符串和数字拼接,ts应该是报错的,因为不符合规则
所以推出重载函数
泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,
而在使用的时候再指定类型的一种特性。
泛型: <自定义的的值> 代表数据类型,后期由你传入的类型决定 比如: <T> <M> <A>
其中 T 代表 Type,在定义泛型时通常用作第一个类型变量名称。但实际上 T 可以用任何有效名称代替。除了 T 之外,以下是常见泛型变量代表的意思:
K(Key):表示对象中的键类型;
V(Value):表示对象中的值类型;
E(Element):表示元素类型。
function f<M>(arg:M):M{
//第一个m是泛型 第二个是传入值的类型 第三个是返回值类型
return arg;
}
console.log(f<string>('hello'));//hello
解释:这里的f<string>是什么类型,上面的M就是什么什么类型,所以后期可指定值类型
泛型约束
// 泛型约束
//在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法:
function test<T>(a: T[]):T{
console.log(a.length);
// return a.length; // 报错: 泛型约束(因为这个时候你还不知道返回值是什么类型)
return a[2]
}
console.log(
test<number>([1,3,54,6]));// 4 54
interface Ilength {
//定义一个接口,用来约束将来的某个类型中必须要有lenght属性
length: number
}
function test<T extends Ilength>(a: T): number {
return a.length;
}
console.log(test<string>("穿过挪威的森林"));//7
console.log(test<number>(123));// !!类型“number”不满足约束“Ilength”
泛型接口
extends(继承)和 implements(实现)的关系
在定义接口时, 为接口中的属性或方法定义泛型类型 在使用接口时, 再指定具体的泛型类型
interface IbaseCRUD<T> {//定义一个泛型接口
data: T[]
add: (t: T) => T
getById: (id: number) => T
}
class User {
id?: number //id主键自增
name: string //姓名
age: number //年龄
constructor(name, age) {
this.name = name
this.age = age
}
}
class UserCRUD implements IbaseCRUD<User> {
//用来保存多个User类型的用户信息对象
data: Array<User> = []
add(user: User): User {
user = { ...user, id: Math.random() }
this.data.push(user)
// console.log('保存user', user.id)
return user
}
//方法根据id查询指定的用户信息对象
getById(id: number): User {
return this.data.find(item => item.id === id)
}
}
//实例化添加用户信息对象的类UserCRUD
const userCRUD = new UserCRUD()
//调用添加数据的方法
userCRUD.add(new User('tom', 12))
const { id } = userCRUD.add(new User('tom2', 13))
console.log(userCRUD.data)
//测试getById方法
const user = userCRUD.getById(id)
console.log("匹配到的内容", user);
泛型类
//定义一个类,类中的属性值的类型是不确定的,方法中的参数及返回值的类型也是不确定
//定义一个泛型类
class GenericNumber<T> {
defaultValue: T
add: (x: T, y: T) => T
}
//在实例化类的对象的时候,在确定泛型的类型
const g1: GenericNumber<number> = new GenericNumber<number>()
//设置属性值
g1.defaultValue = 100
g1.add = function (x, y) {
return x + y
}
console.log("调用并输出", g1.add(10, 20));
console.log("调用并输出", g1.add(g1.defaultValue, 20));
类
es6中类的用法1
使用 class 定义类,使用 constructor 定义构造函数。
通过 new 生成新实例的时候,会自动调用构造函数。
class Animal {
//实例对象属性
name;
//构造器constructor方法是类的默认方法,定义私有的属性
constructor(name) {
this.name = name;
}
//公有的方法
sayHi() {
}
//静态方法
static say() {
}
//class的getter和setter
get name() {
return 'Jack';
}
set name(value) {
console.log('setter1: ' + value);
}
}
//Vue的双向数据绑定的原理,数据劫持,(object.definedProper),就是通过get和set方法实现,只能拦截一次
TypeScript 中类的用法
class Person {
name:string;
constructor(name:string='hello') {//hello是默认值
this.name=name
}
}
let a = new Person();//如果这里传值,就会使用
console.log(a);//Person { name: 'hello' }
console.log(a.name); // Jack
public private 和 protected
TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected。
public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
class Animal {
public name:any;//公有的属性
public constructor(name:any) {
this.name = name;
}
}
let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
console.log(a.name); // Tom
private修饰的属性或方法是私有的,只能在类的内部访问
class Animal {
private name:any;
constructor(name:any) {
this.name = name;
}
sayHi() {
return `My name is ${this.name}`;
}
}
let a = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack
使用 private (私有)修饰的属性或方法,在子类中也是不允许访问的:
class Animal {
private name:any;
public constructor(name:any) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name:any) {
super(name);
console.log(this.name);
}
}
换为protected 受保护的就不会报错了
class Animal {
protected name:any;
public constructor(name:any) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name:any) {
super(name);
console.log(this.name);
}
}
只读属性的初始化操作,要么在声明的时候进行,要么在构造器中进行
class Animal {
readonly name;
public constructor(name) {
this.name = name;
}
}
let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
//报错Cannot assign to 'name' because it is a read-only property.
class Person2 {
//(1) 构造函数中的name参数,一旦使用readonly进行修饰后,那么该name参数可以叫参数属性
//(2) 构造函数中的name参数,一旦使用readonly.进行修饰后,那么Person中就有了一个name的属性成员
//(3) 构造函数中的name参数,一旦使用readonly进行修饰后,外部也是无法修改类中的name属性成员值的
//name;(2)
constructor(readonly name: string = "大甜甜") {
//(1) this.name = name; this.name中name是Person2的参数属性
}
}
//实例化对象
const p = new Person2('小甜甜')
//p.name="真好"//(3)
console.log(p.name)
存取器
TypeScript 支持通过 getters/setters 来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。
class Person {
firstName: string;
lastName: string
constructor(firstName: string, lastName: string) {
this.firstName = firstName;
this.lastName = lastName
}
get fullName() {
return this.firstName + "_" + this.lastName
}
set fullName(val) {
console.log("设置的值", val);
let names = val.split("_")
this.firstName = names[0];
this.lastName = names[1]
}
}
//实例化对象
const p: Person = new Person('东方', "不败")
console.log(p)
p.fullName = "诸葛_孔明"
console.log(p)
静态属性static
静态属性, 是类对象的属性
非静态属性, 是类的实例对象的属性
class Person {
static name1: string = "小甜甜";
constructor() {//构造函数是不能通过static来进行修饰的
}
static sayHi() {
console.log("萨瓦迪卡");
}
}
//var p = new Person();
//---通过实例对象调用属性/方法------
// console.log(p.name); //类型“Person”上不存在属性“name”
//p.sayHi()//属性“sayHi”在类型“Person”上不存在。
//---通过类.静态属性/方法 来访问该成员数据------
Person.name1 = "佐助" //通过类.静态属性来设置该成员数据
console.log(Person.name1);//小甜甜
Person.sayHi()//萨瓦迪卡
这样就不用创建类的实例对象调用属性/方法
抽象类
抽象类:包含抽象方法(抽象方法一般没有任何的具体内容的实现),也可以包含实例方法,抽象类是不能被实例化,为了让子类进行实例化及实现
内部的抽象方法
抽象类的目的或者作用最终都是为子类服务的
abstract class Animal {
// abstract eat() {//抽象方法 --报错-抽象方法不能有具体实现
// console.log("吃");
// }
/*(1) abstract name: string; 没有必要在抽象类中来一个抽象属性让子类去实现,所以不考虑 */
abstract eat()
sayHi() {//实例方法
console.log('你好呀')
}
}
//const ani=new Animal()//报错-- 不能实例化抽象类的对象!
/*定义一个子类(派生类)Dog */
class Dog extends Animal {
// name: string = "小黄";
/*重新的实现抽象类中的方法,此时这个方法就是当前Dog类的实例方法了 */
eat() {
console.log('蘸着吃')
}
}
const dog = new Dog()
dog.eat()
dog.sayHi()//这里调用的是抽象类中的实例方法
//(1) console.log(dog.name);
类型别名
类型别名用来给一个类型起个新名字。
类型别名有时和接口很像,但是可以用于原始值,联合类型,元祖以及其它任何需要手写的类型
通过type 关键字定义类型别名
type Name = string;//给string起了个别名name
type NameResolver = () => string;//类型别名还可以是一个函数(定义一个函数,函数的返回值是string类型,这里的)
箭头如果实在函数表达式中(就是声明函数时使用的),意味着是指定函数返回值的类型,除此以外代表都是箭头函数
type NameOrResolver = Name | NameResolver;//综合类型
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}
console.log(getName("string"字符串)//字符串的情况
let res=getName(function(){//else的情况
return 'hello'
})
console.log(res)//hello
上例中,我们使用 type 创建类型别名。
类型别名常用于联合类型。
声明文件
declare var num:number;//声明一个变量
declare function jQuery(selector: string): any;//声明了一个方法
num = 100;
jQuery('#app')
声明文件: 扩展名时 .d.ts ; 格式: 自定义文件名.d.ts
ts 会自动查找声明文件
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
我们通常这样获取一个 id 是 foo 的元素:
$('#foo');
// or
jQuery('#foo');
但是在 ts 中,编译器并不知道 $ 或 jQuery 是什么东西1:
jQuery('#foo');
// ERROR: Cannot find name 'jQuery'.
这时,我们需要使用 declare var 来定义它的类型2:
declare var jQuery: (selector: string) => any;
jQuery('#foo');
上例中,declare var 并没有真的定义一个变量,只是定义了全局变量 jQuery 的类型,仅仅会用于编译时的检查,在编译结果中会被删除。它编译结果是:
jQuery('#foo');