TS 变量与数据类型
TS 变量语法:
// js: (let 变量名 = 值)
let age = 18
// ts: (let 变量名:变量类型 = 值)
let age:number = 18
// 在 ts 中,为变量指定了类型,就只能给这个变量设置相同的类型的值,否则会报错
let name: string = 'zs'
name = 200 // Error: Type '200' is not assignable to type 'string'
TS 数据类型
- TS 基本类型
如果变量声明和赋值是同时进行的,那么可以省略类型,因为 TS 会自动对类型进行检测
// 1.字符串类型 '' "" ``
let name: string = 'zs'
// 2. 数值类型
let age: number = 20
age = 22.22
age = -1
// 3. 布尔类型
let isApple: boolean = false
isApple = true
// 4. undefined null
let und: undefined = undefined
let nul: null = null
- 数组类型 需要声明数组中元素的类型
// 方式一:
let arr:string[] = ['aaa','bbb'] // 数组中的元素都是字符串
// 方式二:范型数组
let arr:Array<string> = ['aaa','bbb'] // 数组中的元素都是字符串
// 声明字符串类型的数组,不允许存入字符串意外的元素
let arr:Array<string> = ['aaa','bbb',100000] // Error:Type 'number' is not assignable to type 'string'
- 元组 概念:就是一个规定了元素数量和每个元素类型的数组,每个元素的类型可以不同
let tup1: [string, number, boolean] = ['aaa', 12, true] // 数组长度为3,第一个元素是字符串,第二个元素是数值,第四个元素是不二类型
// 当给元组赋值时 如果元素数量不同或者元素类型不同 都会报错
tup1 = ['bbb', 11, 'true'] // Error:Type 'string' is not assignable to type 'boolean'
// 访问元组中的元素,和元组长度
tup1[0] // 'bbb'
tup1.length // 3
为什么要有元组? 因为 TS 中数组元素类型必须相同,如果需要不同类型的元素,就可以使用元组了
特点: 声明时,要指定元素个数 声明时,要为每个元素规定类型
- 枚举 应用场景:性别标识 男,女,未知
// 声明语法:
enum 枚举名{
枚举项1 = 枚举值1,
枚举项2 = 枚举值2
}
// 枚举项一般用英文或数字,枚举值用整数类型
enum Gender{
man : 1,
women : 2,
unknow : 3
}
// 使用默认枚举值:
enum 枚举名{
枚举项1, // 0
枚举项2, // 1
}
// 当不写枚举值时,自动生成从0开始的枚举值
// 声明枚举类型的变量
let userSex:Gender
// 变量枚举赋值
userSex = Gender.man
- any 类型 概念:any 代表任意类型,一般在获取 dom 时使用 我们在接受用户输入 或 第三方代码库时,还不能确定会返回什么类型的值,此时可以使用 any 类型
// 实例:
let txtName: any = document.getElementById('txtN')
- void 类型 概念:void 代表没有类型,一般用在无返回值的函数
// 语法:
function sayHi(): string {
// 标识sayHi函数的返回值时string类型
return 'hello'
}
function sayHi2(): void {
// 表示函数没有返回值
console.log('hello')
}
- never 类型 概念:never 代表不存在的值类型,常用作抛出异常或无限循环的函数返回类型(不会运行结束的函数)
// 语法:
function test(): never {
while (true) {}
}
function test2(): never {
throw new Error('出错了!!!')
}
补充: never 类型是 ts 中的底部类型,所有类型都是 never 类型的父类 所有的 never 类型值都可以赋给任意类型的变量
let x: never = test()
let y: string = test()
any , void , never 的区别? any 不确定是什么类型的时候使用 void 函数没有返回值的时候使用 never 函数内部抛出异常或者死循环的时候使用
TS 函数
语法:
function 函数名(参数:'参数类型',参数:‘参数类型’):'返回值类型'{
return ...
}
函数必须定义返回值类型,如果函数没有返回值,则定义返回值类型为 void 实参和形参的类型要一致 实参和形参的数量要一致
function sum(a: number, b: number): number {
return a + b
}
let res: number = sum(1, 2)
函数可选参数(可选参数可以传也可以不传) 参数类型前加一个问号
function sum(a?: number): number {}
函数参数使用默认值参数 参数如果不传,使用默认值
function 函数名(参数1: 类型 = 默认值1, 形参2: 类型 = 默认值2): 返回值类型 {}
如果不确定传入多少个参数,可以使用剩余参数
function add(a: number, b: number, ...形参3: 类型[]): void {
console.log(a + b)
}
剩余参数 只能 定义一个 剩余参数 只能 定义为数组 剩余参数 只能 定义定义在形参列表后面
总结:类型声明为变量设置了类型,使得变量只能存储某种类型 TS 拥有自动类型判断机制,当声明和赋值同时进行时,TS 编译器会自动判断变量的类型 所以当变量声明和赋值同时进行,可以省略变量声明
类的封装
类 - 批量创建对象
创建对象(构造函数 + new)-----------------------------------------
// 添加成员变量
function City(cName, cLevel) {
this.cName = cName
this.cLevel = cLevel
}
// 添加原型方法
City.prototype.about = function () {
console.log(`城市名称:${this.cName},城市等级:${this.cLevel}`)
}
调用构造函数
let c1 = new City('北京', 1)
// 访问成员变量
console.log(c1.cName) // '北京'
// 调用方法
c1.about() // 城市名称:北京,城市等级:1
创建对象(类 class + TS)--------------------------------------------- 使用 class 关键字来定义类 对象中只要包含两个部分:属性、方法
class Person {
/**
* 直接定义的属性是实例属性,需要通过对象实例去访问
* const per1 = new Person()
* per1.name
* 使用static开头的属性是静态属性(类属性),可以直接通过类去访问
* Person.age
*/
name: string = 'zs' //实例属性 (可读可写)
readonly name: string = 'zs' //实例属性 (只读属性)
static age: number = 18 //类属性
/**
* 直接定义的方法是实例方法,需要通过实例调用
* per1.sayHello()
* 使用static开头的方法是静态方法(类方法),可以直接通过类去调用
* Person.sayHello()
*/
sayHello(){ // 实例方法
console.log('hello')
}
static sayHello(){ // 类方法
console.log('hello')
}
}
new Person 的时候会调用 constructor 函数, constructor 函数可以接收 new 传递进来的参数,给实例属性赋值
class City{
// 成员变量
cname:string;
clevel:number
// 构造函数:做初始化(成员变量必须被赋值,否则会报错)
constructor(cName:string,cLevel:number){
this.cname= cName // this表示当前实例对象
this.clevel = cLevel
}
// 成员方法:定义在类中
about(){
console.log(`城市名称:${this.cName},城市等级:${this.cLevel}`)
}
}
调用
let c1 = new City('北京', 1)
// 访问成员变量
console.log(c1.cName) // '北京'
// 调用方法
c1.about() // 城市名称:北京,城市等级:1
类的继承
// Dog类,Cat类 属性和方法相似,
class Dog {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
sayHello() {
console.log('hello')
}
}
class Cat {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
sayHello() {
console.log('hello')
}
}
// 所以可以抽象出一个父类animal类,
class Animal {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
sayHello() {
console.log('hello')
}
}
// Dog,Cat类继承animal类
class Dog1 extends Animal {}
class Cat1 extends Animal {}
// 使用继承后,子类将会拥有父类所有的属性和方法 (属性方法继承)
// 通过继承可以将多个类中共有的代码写在一个父类中,只需要写一次即可让所有子类同时拥有父类的属性
// 如果希望在子类中添加一些父类中没有的属性和方法,直接在子类中添加即可 (开闭原则:对修改关闭对扩展开放.子类对父类属性方法的扩展)
// 如果子类中添加了和父类中同名的方法,子类方法会覆盖父类的方法 (方法的重写)
super
// 当在子类中想要使引用父类的属性方法时,使用super关键字,super代表当前类的父类
// 子类的构造函数中必须调用父类的构造函数
;() => {
class Animal {
name: string
constructor(name: string) {
this.name = name
}
sayHello() {
console.log('hello')
}
}
class Dog1 extends Animal {
age: number
constructor(name: string, age: number) {
super(name) // 子类调用父类构造函数
this.age = age
}
sayHello(): void {
super.sayHello() // 子类调用父类方法
}
}
}
抽象类
;() => {
class Animal {
name: string
constructor(name: string) {
this.name = name
}
sayHello() {
console.log('hello')
}
}
class Dog1 extends Animal {
age: number
constructor(name: string, age: number) {
super(name) // 子类调用父类构造函数
this.age = age
}
sayHello(): void {
super.sayHello() // 子类调用父类方法
}
}
}
// class Animal 类可以用来创建实例对象,但是创建的实例对象没有意义,所以我们希望禁止Animal类创建实例对象
// 依次可以加 abstract 关键字,放Animal类变为抽象类,抽象类不允许创建实例对象,只能用于子类的继承
/**
* 抽象类:
* 抽象类与其他类的不同 只是不能用来创建对象
* 抽象类是专门用来继承的类
* 抽象类中可以添加抽象方法
*/
;() => {
abstract class Animal {
// 定义抽象类
name: string
constructor(name: string) {
this.name = name
}
sayHello() {
console.log('hello')
}
}
class Dog1 extends Animal {
age: number
constructor(name: string, age: number) {
super(name) // 子类调用父类构造函数
this.age = age
}
sayHello(): void {
super.sayHello() // 子类调用父类方法
}
}
// new Animal() // Error:无法创建抽象类的实例
}
/**
* 抽象方法:
* 抽象方法是abstract开头的方法,没有方法体
* 抽象方法只能定义在抽象类中,
* 子类必须对抽象方法进行重写
*/
;() => {
abstract class Animal {
// 定义抽象类
name: string
constructor(name: string) {
this.name = name
}
abstract sayHello(): void // 定义抽象方法
}
class Dog1 extends Animal {
age: number
constructor(name: string, age: number) {
super(name) // 子类调用父类构造函数
this.age = age
}
sayHello(): void {
// 重写抽象方法
console.log('hello')
}
}
}
接口
;(function () {
// ts类型声明
// 声明一个类型 myType
type myType = {
name: string
age: number
}
// 定义一个myType类型的变量
let aaa: myType = {
name: 'zs',
age: 12,
}
/**
* 接口用来定义一个类的结构,定义了一个类中应该包含哪些属性和方法
* 同时interface可以作为类型声明来使用(注意:type类型声明不可以重名;interface接口可以重名 重名时是将多次生命进行叠加)
*/
interface myInterface {
name: string
age: number
sayHello(): void
}
// 接口可以作为类型声明来规范对象结构
let bbb: myInterface = {
name: 'zs',
age: 12,
sayHello() {},
// gender: string // Error:“gender”不在类型“myInterface”中
// interface作为类型声明,定义的对象属性方法必须与interface中声明的完全一致
}
/**
* 接口可以在定义类的时候限制类的结构
* 接口中的所有属性都不能有实际值
* 接口只定义对象的结构,而不考虑实际值
* 在接口中所有的方法都是抽象方法(抽象类中可以定义抽象方法也可以定义普通方法,接口中只能定义抽象方法)
* 在实现接口时,要将所有的属性都赋值,将所有的方法都实现
*/
// 定义一个类 实现myInterface接口
class Person implements myInterface {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
sayHello(): void {
console.log('hello')
}
// gender:string // 定义接口之外的属性不会报错
}
// 注意:abstract interface只能在TS中使用,js中没有
})()
属性的封装
(function(){
// 属性封装的目的: 让属性变得更加安全
class Person{
name:string;
value:number;
constructor(name:string,value:number){
this.name = name;
this.value = value
}
}
let per1 = new Person('张三',100)
per1.value = -100
/**
* 属性封装的解决的问题:
* 类中定义的属性很容易被修改
* 对于一些比较重要的数据,如果被用户修改为不合法发值,会导致计算出错
* 因此重要属性被轻易修改是不安全的
*/
/**
* 属性修饰符:
* TS中可以在属性前添加属性修饰符
* public 修饰的属性可以在任意位子访问修改 (public是默认值,如果不写修饰符默认是public)
* private 自由属性,只能在当前类内部进行范访问修改,子类里面不能修改访问
* 如果项要外部修改自由属性,可以向外暴露方法,通过方法来修改私有属性
* protected 受保护的属性,只能在当前类和子类中访问
*/
class Person1{
private name:string;
private value:number;
constructor(name:string,value:number){
this.name = name;
this.value = value
}
getValue(){
return this.name
}
setValue(value:number){
if(value>0){
this.value = value
}
}
}
let per = new Person1('zs',12)
per.getValue()
per.setValue(13)
/**
* getter方法用来读取属性
* setter方法用来设置属性
* - 它们被称为属性的存取器
*/
class Person2 {
private _name: string;
private _value: number;
constructor(name: string, value: number) {
this._name = name;
this._value = value
}
get value() {
return this._value
}
set value(value: number) {
if (value > 0) {
this._value = value
}
}
}
let per2 = new Person2('zs', 12)
per2.value
per2.value = 13
// TS中定义属性的语法糖,直接在构造函数形参中定义属性,此时构造函数内部不需要属性赋值
class Person3{
constructor(public name: string, public age:number){
}
}
// ===
class Person33 {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
})()
泛型
/**
* 在定义函数或者类时,如果遇到类型部不确定就可以使用泛型
* 只有在函数被调用时才能确定是什么类型
*/
function fn<T>(a: T): T {
return a
}
// 不指定泛型,TS可以对类型进行推断
fn(10) // 参数为10,是number类型,因此泛型的T就判断为number类型
// 指定泛型
fn<string>('hello')
// T extends Inter 表示泛型T必须是Inter是实现类(子类)
interface Inter {
length: number
}
function fn3<T extends Inter>(a: T): number {
return a.length
}
class Myclass<T> {
name: T
constructor(name: T) {
this.name = name
}
}
const mc = new Myclass<string>('zs')
TS + Vue3 实战应用
// string类型
let type:string = '';
const type:Ref<string> = ref('')
const type = ref<string>('')
// boolean类型
const type:Ref<boolean> = ref(true)
let type1:boolean = false;
// number类型
const type:Ref<number> = ref(1)
let type1:number = 2;
// Array类型
const type:Ref<Array<number>> = ref([1,2,3])
let type1:Array<number> = [1,2,3];
const type:Ref<number[]> = ref([1,2,3])
let type1:number[] = [1,2,3];
// function类型
function fun(name:string,age:number):string{
return name+age;
}
fun('zs',18)
// 对象 类型
function fun(point:{x:number,y:number}){
return '' + point.x + point.y;
}
console.log('fun',fun({x:1,y:2}));
// 对象 可选属性
function fun(point:{x:number,y?:number}){ // y为可选属性,可传可不传(不传的时候y为undefined)
if(point.y!==undefined){
return point.x+point.y
}else{
return point.x
}
}
console.log('fun',fun({x:1,y:2}));
// 联合类型
function fun(id:string|number){
console.log('your id is:'+id)
}
console.log(fun(12));
console.log(fun('12'));
// 类型别名 type (对于常用的复杂类型,定义类型别名)
type Options = {
code:string,
name:string
};
const typeOptions = ref<Options[]>([]);
typeOptions.value = [
{
code:'1',
name:'a'
},
{
code:'2',
name:'b'
},
]
type Id = string | number;
const id:Ref<Id> = ref('1')
const id1 = ref<Id>('1')
// 接口 interface(用于命名对象类型)
interface Person {
name:string,
age:number,
gender?:string // 可选属性
}
const person1:Ref<Person> = ref({
name:'张三',
age:18
})
console.log('person1',person1);
const person2 = ref<Person>({
name:'李四',
age:20
})
console.log('person2',person2);
// 扩展类型别名
type Options = {
code:string,
name:string
};
type Options1 = Options & {
class:string
}
// 扩展接口
interface Person {
name:string,
age:number,
gender?:string // 可选属性
}
interface Animal extends Person {
eat:string
}
// 类型别名 创建后无法修改
// 接口 创建后可以修改
interface Options {
code:string,
}
interface Options { // 向接口中添加新字段
name:string,
}
// 类型断言
type Options = {
name:string
}
const options = ref({} as Options)
const options = ref(<Options>{});
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
// 字面类型(变量值只能是被给出的字面量)
let alignment:'left' | 'center' | 'right' = 'left'
// null undefined
// strictNullChecks 严格模式关闭 null和undefined可以分配给任何类型的变量
// strictNullChecks 严格模式开启 null和undefined只能赋值给any类型
// 非空断言运算符 后缀!
let x:number | null | undefined = 123;
x!.toFixed(2) // 告诉编译器 x 不可能为 null 或 undefined
// 枚举类型
enum gender{
man='男',
woman='女'
}
gender.man // '男'
gender.woman // '女'
const genderMap = {
[gender.man]:'男',
[gender.woman]:'女'
}