Class类(TS -- 8)
ES6
提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过 class
关键字,可以定义类。基本上,ES6 的 class
可以看作只是一个语法糖
,它的绝大部分功能,ES5 都可以做到,新的 class
写法只是让对象原型的写法更加清晰、更像面向对象
编程的语法而已。上面的代码用 ES6 的 “类” 改写
JavaScript写法 //定义类 JavaScript写法 class Person { constructor (name:string,age:number,sub:boolean) { this.name = name this.age = age this.sub = sub } } new Person("小满",22,false) TypeScript写法 //在TypeScript中是需要提前声明类型的 class Person { name:string age:number sub:boolean//没错,没使用标红的是这些 constructor (name:string,age:number,sub:boolean) { this.name = name this.age = age this.sub = sub//上面定义了变量就需要使用,如果没用使用的话声明的变量就会标红(就算不标红不提示,真运行下去也会报错),不能就那么放着,要么就用上,要么就给他个默认值0塞着 } } new Person("小满",22,false)
public
public内部外部都可以访问,如果定义了public,像p就能够访问constructor内部的变量了。当然,默认情况下也是public
//在TypeScript中是需要提前声明类型的 class Person { public name:string public age:number public sub:boolean//没错,没使用标红的是这些 constructor (name:string,age:number,sub:boolean) { this.name = name this.age = age this.sub = sub//上面定义了变量就需要使用,如果没用使用的话声明的变量就会标红(就算不标红不提示,真运行下去也会报错),不能就那么放着,要么就用上,要么就给他个默认值0塞着 } } let p = new Person("小满",22,false) p.age p.name p.sub//都可以访问
private
private 私有变量只能在内部访问 //在TypeScript中是需要提前声明类型的 class Person { private name:string private age:number private sub:boolean//没错,没使用标红的是这些 constructor (name:string,age:number,sub:boolean) { this.name = name this.age = age this.sub = sub//上面定义了变量就需要使用,如果没用使用的话声明的变量就会标红(就算不标红不提示,真运行下去也会报错),不能就那么放着,要么就用上,要么就给他个默认值0塞着 } } let p = new Person("小满",22,false) p.age p.name p.sub//都访问不到了
protected
protected内部和子类中访问
provate跟protectd他们的区别是一个是只能在内部使用,一个是内部与子类访问,例子如下
//在TypeScript中是需要提前声明类型的 class Person { protected name:string private age:number public sub:boolean//没错,没使用标红的是这些 constructor (name:string,age:number,sub:boolean) { this.name = name this.age = age this.sub = sub//上面定义了变量就需要使用,如果没用使用的话声明的变量就会标红(就算不标红不提示,真运行下去也会报错),不能就那么放着,要么就用上,要么就给他个默认值0塞着 } } class Man extends Person{ constructor(){ super("小满",22,false) this.name this.sub//这两个都可以访问到,this.age访问不到。因为age是private,private只能在内部使用而不能在子类访问,Man是Person的子类 } } let p = new Person("小满",22,false) p.age p.name p.sub
static 静态属性 和 静态方法
- 静态属性和非静态属性的区别:
- 在内存中存放的位置不同:所有 static 修饰的属性和方法都存放在内存的方法区里,而非静态的都存在堆内存中
- 出现的时机不同:静态属性和方法在没创建对象之前就存在,而非静态的需要在创建对象才存在
- 静态属性是整个类都公用的
- 生命周期不一样,静态在类消失后被销毁,非静态在对象销毁后销毁
- 用法:静态的可以直接通过类名访问,非静态只能通过对象进行访问
- 使用
static
注意事项
- 带静态修饰符的方法只能访问静态属性
- 非静态方法既能访问静态属性也能访问非静态属性
- 非静态方法不能定义静态变量
- 静态方法不能使用 this 关键字
- 静态方法不能调用非静态方法,反之可以
- 父子类中静态和非静态的关系
- 对于非静态属性,子类可以继承父类非静态属性,但是当父子类出现相同的非静态属性时,不会发生子类的重写并覆盖父类的非静态属性,而是隐藏父类的非静态属性
- 对于非静态方法,子类可以继承并重写父类的非静态方法
- 对于静态属性,子类可以继承父类的静态属性,但是如何和非静态属性一样时,会被隐藏
- 对于静态方法,子类可以继承父类的静态方法,但是不能重写静态方法,同名时会隐藏父类的
注:静态属性、静态方法、非静态属性都可以被继承和隐藏,但是不可以被重写,非静态方法可以被重写和继承
- 静态代码块的作用:
一般情况下,有些代码需要在项目启动的时候就执行,这时候就需要静态代码块,比如一个项目启动需要加载配置文件,或初始化内容等。 - 静态代码块不能出现在任何方法体内
对于普通方法:普通方法是需要加载类 new 出一个实例化对象,通过运行这个对象才能运行代码块,而静态方法随着类加载就运行了。
对于静态方法:在类加载时静态方法也加载了,但是必须需要类名或者对象名才可以访问,相比于静态代码块,静态方法是被动运行,而静态代码块是主动运行 - 静态代码块不能访问普通变量
普通变量只能通过对象调用的,所以普通变量不能放在静态代码块中。
普通代码块和构造代码块
- 静态代码块和构造代码块在声明上少一个 static 关键字
- 执行时机:
构造代码块在创建对象时被调用,每次创建对象都会调用一次,且优先于构造函数执行。
注:不是优先于构造函数执行,而是依托于构造函数,如果不创建对象就不会执行构造代码块 - 普通代码块和构造代码块的区别在于,构造代码块是在类中定于的,而普通代码块是在方法体中定义的,执行顺序和书写顺序一致。
执行顺序
静态代码块 > 构造代码块 > 构造函数 > 普通代码块
class Person { protected name:string private age:number public sub:boolean//没错,没使用标红的是这些 static aaa:string = '123456'//静态属性 constructor (name:string,age:number,sub:boolean) { this.name = name this.age = age this.sub = sub//上面定义了变量就需要使用,如果没用使用的话声明的变量就会标红(就算不标红不提示,真运行下去也会报错),不能就那么放着,要么就用上,要么就给他个默认值0塞着 this.run()//会报错,调用不了。互斥的,不能够通过静态函数去访问内部的变量,或者是在内部的变量去调用外部的静态函数 Person.run()//只能这样去调用 } static run (){ this.dev()//静态函数之间可以互相调用 this.aaa//用this的话只能访问上面static类型的,其他的不管是public还是private或者是protected都是不能够访问的(会报不存在属性的错误) 因为这里的this指的是当前这个类,而构造函数里面的this指的是新的实例对象 return '789' } static dev(){ this.aaa//静态函数之间可以互相调用 return 'dev' } } console.log(Person.run())//返回789 Person.aaa//能够直接访问,不需要再new一下 console.log(Person.aaa) let p = new Person("小满",22,false)
interface 定义 类
ts interface 定义类 使用关键字 implements 后面跟 interface 的名字多个用逗号隔开 继承还是用 extends
通过接口去约束类 interface Person{ run(type:boolean):boolean } class Man implements Person{//会提示我们Man中缺少属性run,但类型Person中需要该属性 } //通过接口去约束类 interface Person{ run(type:boolean):boolean } interface H{ set():void } class Man implements Person,H{//会报错,提示我们缺少set属性 run(type:boolean):boolean{ return type } } interface Person{ run(type:boolean):boolean } interface H{ set():void } class A{//也可以使用继承去使用 params:string constructor(params){ this.params = params } } class Man extends A implements Person,H{ run(type:boolean):boolean{ return type } set(){ //啥也没有,这就是用接口去描述类 } }
抽象类(TypeScript8)
用关键词abstract
修饰的类称为 abstract 类(抽象类)
应用场景如果你写的类实例化之后毫无用处此时我可以把他定义为抽象类
或者你也可以把他作为一个基类 -> 通过继承一个派生类去实现基类的一些方法
对于 abstract 方法只允许声明,不允许实现(因为没有方法体)(毕竟叫抽象,当然不能实实在在的让你实现),并且不允许使用 final 和 abstract 同时修饰一个方法或者类,也不允许使用 static 修饰 abstract 方法。也就是说,abstract 方法只能是实例方法,不能是类方法。
abstract class A{ name:string construct(name:string){//construct:构造器 this.name = name } //abstract getName(){//方法getName不能具有实现,因为它标记为抽象。定义抽象类的函数 // return 213 //} setName(name:string){ this.name = name } abstract getName():string//抽象类 } class B extends A{//派生类。定义了抽象类必须在派生类里实现 //B类是继承A类的,此时A类就是一个抽象类 constructor(){ super('小满') } getName():string{ return this.name } } //此时A类是无法被创建实例的(new A),也就是无法创建抽象类的实例 //B类是可以创建实例的(new B) let b = new B b.setName("小满2")//通过抽象类的设置,成功修改掉子类的内容 // setName(name:string){ // this.name = name // } console.log(b.getName())
元组类型(TS -- 9)
数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。
let arr:[string,number] = ['小满',22]//这样的方式就叫做元组,定义了每个位置需要满足的不同类型 arr[0].length//有 arr[1].length//无,因为上面的定义类型会自动帮我们推断是否有该方法 //Number 类型是没有 length 属性的
越界的元组
当添加的元组越界的时候,越界的类型会被限制为元组类型中每个类型的联合类型
let arr:[string,number] = ['小满',22]//这样的方式就叫做元组,定义了每个位置需要满足的不同类型 arr.push(true)//会报错,因为类型boolean参数不能赋值给string|number的类型 //这个就是元组对越界元素的处理 arr.push('111',2222)//这种就可以 //也可以对二维数组进行限制规定类型
枚举类型(TS -- 10)
在 javaScript 中是没有枚举
的概念的 TS 帮我们定义了枚举这个类型
enum 关键字定义枚举
数字定义枚举
默认从0开始的
enum Color{ red, green, blue } console.log(Color.red,Color.blue,Color.green)//能够得到他们的顺序数字,这里返回0,2,1
增长枚举
能够通过自定义开头决定从哪个数字开始枚举,其他位置的都可以定义,后面的数字就按顺序枚举
enum Color{ red=2, green, blue } console.log(Color.red,Color.blue,Color.green)//能够得到他们的顺序数字,这里返回2,4,3
字符串枚举
字符串枚举的概念很简单。 在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。
由于字符串枚举没有自增长的行为,字符串枚举可以很好的序列化。 换句话说,如果你正在调试并且必须要读一个数字枚举的运行时的值,这个值通常是很难读的 - 它并不能表达有用的信息,字符串枚举允许你提供一个运行时有意义的并且可读的值,独立于枚举成员的名字。
enum Types{ Red = 'red', Green = 'green', BLue = 'blue' }
异构枚举
枚举可以混合字符串和数字成员
enum Types{ No = "No", Yes = 1, } console.log(Types.NO,Types.Yes)
接口枚举
定义一个枚举 Types 定义一个接口 A 他有一个属性 red 值为 Types.yyds
声明对象的时候要遵循这个规则
enum Color{ no = "NO", yes = 1 } interface A{ red:Color.yes } let B:A{ red:Color.yes //或者直接red:1,只能填入这两个内容其中之一,其他的会报错 }
const枚举
let 和 var 都是不允许的声明只能使用 const
大多数情况下,枚举是十分有效的方案。 然而在某些情况下需求很严格。 为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用 const
枚举。 常量枚举通过在枚举上使用 const
修饰符来定义
const 声明的枚举会被编译成常量
普通声明的枚举编译完后是个对象
const enum Types{//有没有const决定是编译成对象还是编译成常量 sucess, fail } let code:number = 0 if(code === sucess){//是能执行的 console.log("我在人民广场吃炸鸡") }
反向映射
它包含了正向映射( name
-> value
)和反向映射( value
-> name
)
要注意的是 不会为字符串枚举成员生成反向映射。
enum Types{ one } let success:number = Types.success console.log(success)//读取得出来为0 enum Types{ success } let success:number = Types.success let key = Types[success] console.log(`value---${success}`,`key----${key}`)//value---0,key----success
类型推论|类型别名(TS -- 11)
类型推论
我声明了一个变量但是没有定义类型
TypeScript
会在没有明确的指定类型的时候推测出一个类型,这就是类型推论
let str = "小满"
str = 123//会报错,虽然我们没用明确限制类型,但是TS编辑器会自动推论为string类型。就不能够在赋值给别的类型
如果你声明变量没有定义类型也没有赋值这时候 TS 会推断成 any 类型可以进行任何操作
let str//为any类型 str = 123 str = "马杀鸡" str = false str = []
联合类型
指定多种类型,在前文有提到
type s = string|number let str:s = "永恒的紫罗兰花园" let num:s = 520//这有这两种类型可以
函数式的类型别名
type 关键字(可以给一个类型定义一个名字)多用于符合类型,但也可以要求有固定的东西
定义类型别名
type str = string let s:str = "我是小满" console.log(s);
定义函数别名
type str = () => string let s: str = () => "我是小满" console.log(s);
定义联合类型别名
type str = string | number let s: str = 123 let s2: str = '123' console.log(s,s2);
定义值的别名
type value = boolean | 0 | '213' let s:value = true //变量s的值 只能是上面value定义的值
never类型(TS -- 12)
TypeScript将使用 never 类型来表示不应该存在的状态 返回never的函数必须存在无法达到的终点 function error(message:string):never {//因为必定抛出异常,所以 error 将不会有返回值 throw new Error(message) } function loop():never{ while(true){ //因为这个是死循环,永远不会去返回的 } } interface A{ type:"保安" } interface B{ type:"草莓" } interface C{ type:"卷心菜" } type All = A|B function type(val:All){ while(val.type){ case "保安":break case "草莓":break //case "卷心菜":break default://兜底机制,此时C没有用上就会报错提示。这就算never的作用 const check:never = val break } }
Symbol类型
symbol
是一种新的原生类型,就像 number
和 string
一样
symbol类型的值是通过
Symbol构造函数创建的
可以传递参做为唯一标识 只支持 string 和 number 类型的参数
let s:symbol = Symbol('小满') let num:symbol = Symbol('小满') let obj = { [num] = "value"//Symbol [s] = "草莓"//Symbol name:"小满" sex:"男" } console.log(obj.[num])//取到value console.log(s,num)//返回Symbol(小满)Symbol(小满) console.log(s === num)//false //这个值看似一样,其实因为内存地址指针位置不同,所以是唯一值 for(let key in boj){ console.log(key) }//只会打印出name跟sex,[num]与[s]将打印不出来 console.log(Object.keys(obj))//["name","sex"] console.log(Object.getOwnPropertyNames(obj))//["name","sex"],跟上面一样,打印不出来 console.log(JSON.stringify(obj));//["name":"小满","sex":"男"],一样打印不出来
能够读取到Symbol的两种方式
静态方法 Reflect.ownKeys()
返回一个由目标对象自身的属性键组成的数组(Array)。
语法:Reflect.ownKeys(target) => target
获取自身属性键的目标对象。
Reflect.ownKeys
方法返回一个由目标对象自身的属性键组成的数组。它的返回值等同于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
。
console.log(Object.getOwnPropertySymbol(obj));//能打印出来两个Symbol,另外两个普通的不会打印出来 Reflect.ownKeys()//此属性是将所有的属性都列出来 console.log(Reflect.ownKeys())//四个全部圆满的打印出来
迭代器|生成器(TS -- 13)
迭代器:Symbol.iterator
迭代器(Iterator)是⼀种对象,它能够⽤来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址
通俗点说,迭代器表现的像指针,读取集合或者数组中的⼀个值,读完以后⼜指向下⼀条数据,⼀个个数过去。
生成器: for of
迭代器
迭代器
Interator 的用法
1.Interator
是 es6 引入的一种新的遍历机制。两个核心:
(1) 迭代器是一个统一的接口,它的作用是使各种数据结构
可被便捷的访问,它是通过一个键为 Symbol.iterator 的方法来实现。
(2) 迭代器是用于遍历
数据结构元素的指针(如数据库中的游标)。
使用迭代
- 使用
Symbol.interator
创建一个迭代器 - 调用 next 方法向下迭代,next 方法会返回当前的位置
- 当 done 为 true 时则遍历结束
注意点:
- 在迭代器迭代元素的过程中,不允许使⽤集合对象改变集合中的元素个数,如果需要添加或者删除只能使⽤迭代器的⽅法操作。
- 如果使⽤了集合对象改变集合中的元素个数那么就会报错:不改变个数即可,替换也可以的。
- 迭代器的⽣存周期为创建到使⽤结束的时段。
- foreach : Iterator 的封装变形,变得⽐ Iterator更简单。但是他也需要知道数组或集合的类型。并且,Iterator 需要注意的,foreach
同样需要注意。
- 存在interator迭代器的有 =>
- 数组[]里能够找到 Symbol(Symbol.interator)
- argument内找到 Symbol(Symbol.interator)
- NodeList内找到Symbol(Symbol.interator)
- new set()内的Prototype下一层Symbol(Symbol.interator)
- new Map同理,一样有Symbol(Symbol.interator)
let arr:Array<number> = [1,5,6] let it:Intertor<number> = arr[Symbol.interator]()//注意这里的接收类型<number>是固定要写的 //next一次只遍历一个数,下一次调用将从上一次遍历到的位置开始下一个 console.log(iterator.next()); //{ value: 1, done: false } console.log(iterator.next()); //{ value: 5, done: false } console.log(iterator.next()); //{ value: 6, done: false } console.log(iterator.next()); //{ value: undefined, done: true } //返回的有两个属性,一个value,一个done。value当读取到值的时候,done为false、读取不到为true type mapKeys = string|number//相当于起别名,在下方使用的时候集合了string与number就会相对方便不少 let set:Set<number> = new Set([1,2,3]) let map:Map<mapKeys,mapKeys> = new Map()//这里断言两个mapKeys,一个对应key,一个对应value map.set('1','小满') map.set('2','看看腿') //小迭代器的实现 function gen(erg:any){//这里定义为any类型是因为上面要传到这里的有多种不同类型 let it:interator<any> = erg[Symbol.interator]() let next:any = {done:false} while(!next.done){//判断next,由于next默认为fasle,while循环只有true会通过,所以需要取反 next = it.next()//刚开始是声明next给个默认值,等到开始循环的时候再把真正的值赋给他 if(!next.done){ console.log(next); } } } gen(arr)//调用第一个代码块的arr,输出了与console.log(iterator.next());一样的内容 //对象是不支持迭代器的使用的,其实我们在控制台输出一个对象,查找他内置的属性,也是找不到Symbol.interator的
Symbol列表
Symbol.hasInstance
方法,会被 instanceof 运算符调用。构造器对象用来识别一个对象是否是其实例。
Symbol.isConcatSpreadable
布尔值,表示当在一个对象上调用 Array.prototype.concat 时,这个对象的数组元素是否可展开。
Symbol.iterator
方法,被 for-of 语句调用。返回对象的默认迭代器。
Symbol.match
方法,被 String.prototype.match 调用。正则表达式用来匹配字符串。
Symbol.replace
方法,被 String.prototype.replace 调用。正则表达式用来替换字符串中匹配的子串。
Symbol.search
方法,被 String.prototype.search 调用。正则表达式返回被匹配部分在字符串中的索引。
Symbol.species
函数值,为一个构造函数。用来创建派生对象。
Symbol.split
方法,被 String.prototype.split 调用。正则表达式来用分割字符串。
Symbol.toPrimitive
方法,被 ToPrimitive 抽象操作调用。把对象转换为相应的原始值。
Symbol.toStringTag
方法,被内置方法 Object.prototype.toString 调用。返回创建对象时默认的字符串描述。
Symbol.unscopables
对象,它自己拥有的属性会被 with 作用域排除在外。