TS类 class ts 写法 | 青训营笔记

623 阅读13分钟

类 class ts 写法

一、定义一个类

ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已

  • ts中不允许在constructor中定义变量,需要在constructor上方进行先声明
class Person {
    constructor (name,age) {
        this.name = name  // 报错,类型“Person”上不存在属性“name”。
    }
    run () {

    }
}
  • 定义了变量不用也会报错 , 通常是给默认值 或者 进行赋值操作
class Person {
    name:string 
    age:number // 报错,属性“age”没有初始化表达式,且未在构造函数中明确赋值。
    constructor (name:string) {
        this.name = name
    }
}
  • 正确写法
class Person {
    name:string
    age:number
    constructor (name:string,age:number) {
        this.name = name
        this.age = age
    }
}

二、类修饰符

总共有三个 public private protected

  • public:默认修饰符,他可以让我们定义的变量或者方法在 类内部 类外部 以及 子类 都可以访问
  • private:使用private修饰的变量或方法只能在类内部进行访问,子类和外部都不可以访问
  • protect:使用protect修饰的变量或方法只能在类内部和子类进行访问,类外部不可以访问
class Person {
    private name:string
    private age:number
    constructor (name:string,age:number) {
        this.name = name
        this.age = age
    }
    private init():void {
        this.name += '你好啊'
    }
    protected setAge():void {
        this.age += 1
    }
}

let person_s = new Person('小明',18)

person_s.age // 报错,属性“age”为私有属性,只能在类“Person”中访问。
person_s.setAge() // 报错,属性“setAge”受保护,只能在类“Person”及其子类中访问。

class Student extends Person {
    stuNum:string
    constructor (stuNum:string,name:string,age:number) {
        super(name,age)
        this.stuNum = stuNum
    }
    setStuInfo(){
        this.age += 1 // 报错,属性“age”为私有属性,只能在类“Person”中访问
        this.setAge() // protected修饰的方法可以在子类中使用
        this.init() // 报错,属性“init”为私有属性,只能在类“Person”中访问。
    }
    getStuInfo(){
        console.log(this.stuNum);
    }
    
}

三、static 静态属性和静态方法

静态属性是整个类共有的属性。举个例子才好理解:有一个学生Student类,里面有nameagecount(班级学生数量)等的属性。这里的nameage是一个学生所特有的,但是count呢?它不是,我们不希望每一个学生拥有不同的学生的数量,所以我们把count定义成static类型的数据

  • 1.我们用static 定义的属性 不可以通过this 去访问 只能通过类名去调用
class Vue {
    option:Options
    static version:string
    constructor(option:Options){
        this.option = option
    }
}
const vue = new Vue({el:"#app"})

vue.version // 报错,属性“version”在类型“Vue”上不存在。你的意思是改为访问静态成员“Vue.version”吗?
  • 2.static 静态函数 同样也是不能通过this 去调用 也是通过类名去调用
class Vue {
    name:string
    option:Options
    static version:string
    constructor(option:Options,name:string){
        this.option = option
        this.name = name
    }
    static run () {
        return console.log(this.name);
    }
}
const vue = new Vue({el:"#app"},'name')

// vue.version // 报错,属性“version”在类型“Vue”上不存在。你的意思是改为访问静态成员“Vue.version”吗?
vue.run() // 报错,属性“run”在类型“Vue”上不存在。你的意思是改为访问静态成员“Vue.run”吗?
  • 3.如果两个函数都是static 静态的是可以通过this互相调用
class Vue {
    name:string
    option:Options
    static version:string
    constructor(option:Options,name:string){
        this.option = option
        this.name = name
    }
    static run () {
        return this.output();
    }
    static output() {
        console.log(this.name);
    }
}

四、interface定义类

ts interface 定义类 使用关键字 implements 后面跟interface的名字 多个用逗号隔开 继承还是用extends

interface ReactCls{
    reactName:string
    init():void
}

class Vue {
    name:string
    option:Options
    static version:string
    constructor(option:Options,name:string){
        this.option = option
        this.name = name
    }
    static run () {
        return this.output();
    }
    static output() {
        console.log(this.name);
    }
}

class React extends Vue implements ReactCls {
    reactName:string

    constructor(reactName:string){
        super({el:"#app"},"name")
        this.reactName = reactName
    }
    init:void // 报错,函数实现缺失或未立即出现在声明之后,不能只声明,要有具体实现
    init():void{
        
    }

}

五、抽象类

应用场景如果你写的类实例化之后毫无用处此时我可以把他定义为抽象类 或者你也可以把他作为一个基类-> 通过继承一个派生类去实现基类的一些方法

  • 1.抽象类无法被实例化
abstract class A {
   public name:string
   
}
 
new A() // 报错,无法创建抽象类的实例。
    1. 我们定义的抽象方法必须在派生类实现
abstract class A {
    name: string
    constructor(name: string) {
        this.name = name
    }
    print(): string {
        return this.name
    }

    abstract getName(): string
}

class B extends A {
    constructor() {
        super('大大大大')
    }
    getName(): string {
        return this.name
    }
}

六、简单vue实现

// 挂载点
interface Options {
    el: string | HTMLElement
}

// vue类 类型约束 接口
interface VueCls {
    options:Options
    init():void
}

// 结点类型约束
interface Vnode {
    tar:string, // 目标节点
    text?:string, // 节点内容
    children?:Vnode[] // 子节点
}


// 虚拟dom
class Dom {
    // 创建元素结点
    createElement(el:string) {
        return document.createElement(el)
    }

    // 设置节点内容
    setText(el:HTMLElement, text?:string | null) {
        if(text){
            el.textContent = text
        }
    }

    render(data:Vnode) {
        let root = this.createElement(data.tar)
        if(data.children &&  Array.isArray(data.children)){
            data.children.forEach(item => {
                let child = this.render(item)
                root.appendChild(child)
            })
        } else {
            this.setText(root, data.text)
        }
        return root
    }
}


class Vue extends Dom implements VueCls {
    options: Options
    constructor (options: Options) {
        super() // 父类的prototype.constructor.call
        this.options = options
        this.init()
    }
    init():void {
        let data:Vnode = {
            tar:'div',
            children:[
                {
                    tar:'h1',
                    text:'子节点1'
                },
                {
                    tar:'h2',
                    text:'子节点2'
                }
            ]
        }
        let app = typeof this.options.el == 'string' ?  document.querySelector(this.options.el):this.options.el
        app?.appendChild(this.render(data))
    }
}

new Vue({
    el:"#app"
})

Symbol、生成器、迭代器、Set、Map等等

一、元组类型

元组就是数组的变种

  • 元组与集合的不同之处在于,
  • 元组中的元素类型可以是不同的,
  • 而且数量固定。
  • 元组的好处在于可以把多个元素作为一个单元传递。
  • 如果一个方法需要返回多个值,可以把这多个值作为元组返回,
  • 而不需要创建额外的类来表示。
let arr: [number, boolean] = [1, false]

let arr1: readonly [number, boolean, undefined] = [1, true, undefined]
  • 元组类型还可以支持自定义名称和变为可选的
  • 元组成员必须全部具有或全部不具有名称。
let arr2: [x: number, y?: string] = [1]
  • 应用场景 例如定义excel返回的数据
let excel: [string, string, number, string][] = [
    ['title', 'name', 1, '123'],
    ['title', 'name', 1, '123'],
    ['title', 'name', 1, '123'],
    ['title', 'name', 1, '123'],
    ['title', 'name', 1, '123'],
    ['title', 'name', 1, '123'],
    ['title', 'name', 1, '123'],
]
  • 总结:元组就像是各种类型混合的变形数组而且其元素数量是预先设置好的,但可以设置个别元素为可选元素,元组成员必须全部具有或全部不具有名称

二、枚举类型

枚举是 C 语言中的一种基本数据类型,用于定义一组具有离散值的常量。,它可以让数据更简洁,更易读。 枚举类型通常用于为程序中的一组相关的常量取名字,以便于程序的可读性和维护性。 定义一个枚举类型,需要使用 enum 关键字,后面跟着枚举类型的名称,以及用大括号 {} 括起来的一组枚举常量。每个枚举常量可以用一个标识符来表示,也可以为它们指定一个整数值,如果没有指定,那么默认从 0 开始递增。

  • 1.数字枚举
enum Types {
    Red,
    Blue,
    Green
}
  • 下面代表从1开始递增
enum Types2 {
    Red = 1,
    Blue,
    Green
}
  • 2.字符串枚举

字符串枚举的概念很简单。 在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。

enum Types3 {
    num,
    Red = 'red',
    Blue = 'blue',
    Green = 'green'
}
  • 由于字符串枚举没有自增长的行为,

  • 字符串枚举可以很好的序列化。

  • 换句话说,如果你正在调试并且必须要读一个数字枚举的运行时的值,

  • 这个值通常是很难读的 - 它并不能表达有用的信息,

  • 字符串枚举允许你提供一个运行时有意义的并且可读的值,独立于枚举成员的名字。

  • 3.异构枚举

混合字符串和数字成员

enum Types4 {
    No = 'no',
    Yes = 1
}
  • 4.接口枚举

定义一个枚举Types定义一个接口A 他有一个属性red 值为Types.yyds 声明对象的时候要遵循这个规则

enum Types5 {
    yyds,
    dddd
}
interface A {
    red: Types5.yyds
}

let obj: A = {
    red: Types5.yyds
}
  • 5.const枚举

  • letvar 都是不允许的声明只能使用 const

  • 大多数情况下,枚举是十分有效的方案

  • 然而在某些情况下需求很严格

  • 为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问

  • 我们可以使用 const 枚举。 常量枚举通过在枚举上使用 const 修饰符来定义

  • const 声明的枚举会被编译成常量

const enum Types6 {
    No = 'no',
    Yes = 1
}
console.log(Types6.No);

// js
// console.log("no" /* Types6.No */);
  • 普通声明的枚举编译完后是个对象
enum Types7 {
    No = 'no',
    Yes = 1
}
console.log(Types7.No);

// js
// var Types7;
// (function (Types7) {
//     Types7["No"] = "no";
//     Types7[Types7["Yes"] = 1] = "Yes";
// })(Types7 || (Types7 = {}));
// console.log(Types7.No);
  • 6.反向映射
enum Enum {
    fall
}
let a = Enum.fall
console.log(a);  // 0
let namess = Enum[a]

console.log(namess); // fall

// js
// var Enum;
// (function (Enum) {
//     Enum[Enum["fall"] = 0] = "fall";
// })(Enum || (Enum = {}));
// let a = Enum.fall;
// console.log(a); // 0
// let namess = Enum[a];
// console.log(namess); // fall
  • 总结
  • 1.枚举类型通常用于为程序中的一组相关的常量取名字,以便于程序的可读性和维护性,默认从 0 开始递增
  • 2.字符串枚举允许你提供一个运行时有意义的并且可读的值,独立于枚举成员的名字
  • 3.const枚举,const 声明的枚举会被编译成常量,在某种要求非常苛刻的情况下,可以使用const枚举
  • 4.反向映射,Enum[Enum["fall"] = 0] = "fall"

三、类型别名

  • 1.类型推论

我声明了一个变量但是没有定义类型,TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论

let str11 = 'sss' // let str11: string
// str11 = 12333  // 不能将类型“number”分配给类型“string”。

如果你声明变量没有定义类型也没有赋值这时候TS会推断成any类型可以进行任何操作

let aaaa;  // let aaaa: any

aaaa = 1
aaaa = true
aaaa = '123' // 都不会报错
  • 2.类型别名

type 关键字(可以给一个类型定义一个名字)多用于复合类型

  • 1)定义类型别名
type str = string

let str222: str = '111'  // let str222: string
  • 2)定义函数别名
type fn = () => string

// let strFn:fn = () => 111 // 不能将类型“number”分配给类型“string”
let strFns: fn = () => '111'
  • 3)定义联合类型别名
type abc = string | number

let sabc: abc = 123

let nabc: abc = '123'
  • 4)定义值的别名
type value = 1 | '213' | true

// let conss:value = '123'  // 不能将类型“"123"”分配给类型“value”
let conss: value = 1
  • 3.typeinterface 的区别

  • 1)interface可以继承,type只能通过 & 交叉类型合并

  • 2)type可以定义联合类型并可以使用一些操作符,interface不行

  • 3)interface遇到重名接口会合并,type遇到重复会报错

  • 4.type高级用法

左边的值会作为右边值的子类型遵循图中上下的包含关系

type a1 = 1 extends number ? 1 : 0 //1

type a2 = 1 extends Number ? 1 : 0 //1

type a3 = 1 extends Object ? 1 : 0 //1

type a4 = 1 extends any ? 1 : 0 //1

type a5 = 1 extends unknown ? 1 : 0 //1

type a6 = 1 extends never ? 1 : 0 //0
  • 层级关系 上包含下
unknown any
Object 
Number String Boolean
number string boolean
1 'test' true
never

never 类型 TypeScript 将使用 never 类型来表示不应该存在的状态 返回never的函数必须存在无法达到的终点 因为必定抛出异常,所以 error 将不会有返回值

// 因为必定抛出异常,所以 error 将不会有返回值
function error(message: string): never {
    throw new Error(message);
}

// 因为存在死循环,所以 loop 将不会有返回值
function loop(): never {
    while (true) {
    }
}

// never 与 void 的差异

//void类型只是没有返回值 但本身不会出错
function Void(): void {
    console.log();
}

//只会抛出异常没有返回值
function Never(): never {
    throw new Error('aaa')
}

// never 类型的一个应用场景

type A2 = '小满' | '大满' | '超大满' 
 
function isXiaoMan(value:A2) {
   switch (value) {
       case "小满":
           break 
       case "大满":
          break 
       case "超大满":
          break 
       default:
          //是用于场景兜底逻辑
          const error:never = value;
          return error
   }
}

五、Symbol

自ECMAScript 2015起,symbol成为了一种新的原生类型,就像number和string一样。 symbol类型的值是通过Symbol构造函数创建的。 可以传递参做为唯一标识 只支持 string 和 number类型的参数

let sym1 = Symbol(1)
let sym2 = Symbol(1)
console.log(sym1 === sym2);

console.log(Symbol.for('sym1') === Symbol.for('sym1'));


let objss = {
    [sym1]:1,
    [sym2]:2,
    name:'111'
}

console.log(objss[sym1]);
  • 1.Symbol的值是唯一的
const sym3 = Symbol(1)
const sym4 = Symbol(1)
// console.log(sym3 === sym4); // 本身为false 此比较似乎是无意的,因为类型“typeof sym3”和“typeof sym4”没有重叠。
  • 2.用作对象属性的键
let sym = Symbol()

let obj_1 = {
    [sym]:'value'
}

console.log(obj_1[sym]);  // 'value'

使用symbol定义的属性,是不能通过如下方式遍历拿到的

const symbol1 = Symbol(1)
const symbol2 = Symbol(2)

const obj_2 = {
    [symbol1]:'111',
    [symbol2]:'222',
    age:19,
    sex:1
}

for(let key in obj_2){
    console.log(key)
}
// age
// sex


console.log(Object.keys(obj_2));  // [ 'age', 'sex' ]

console.log(Object.getOwnPropertyNames(obj_2)); // [ 'age', 'sex' ]

console.log(JSON.stringify(obj_2)); // {"age":19,"sex":1}

如何拿到

console.log(Object.getOwnPropertySymbols(obj_2)); // [ Symbol(1), Symbol(2) ]

es6 的 Reflect 拿到对象的所有属性

console.log(Reflect.ownKeys(obj_2));   // [ 'age', 'sex', Symbol(1), Symbol(2) ]

六、Symbol.iterator 迭代器 和 生成器 for of

支持遍历大部分类型迭代器 arr nodeList argumetns set map 等

let arr_it = [1,2,3,4]

let iterator = arr_it[Symbol.iterator]();


console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: 4, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

测试迭代器,定义map,set,实现迭代器原理

interface Item {
    name:string,
    age:number
}

let array:Array<Item> = [{name:'111',age:11},{name:'222',age:22},{name:'333',age:33},{name:'444',age:44}]


let set:Set<number> = new Set([1,1,1,2,2,2,3,3,3])

console.log(set);  // Set(3) { 1, 2, 3 }

type mapType = number | string

let map:Map<mapType,mapType> = new Map()

map.set('111',11)
map.set('222',22)

console.log(map);  // Map(2) { '111' => 11, '222' => 22 }

console.log(map.get('111'));  // 11

function gen(arr:any):void {
    let it:Iterator<any> = arr[Symbol.iterator]();
    let next:any = { done:false }
    while(!next.done){
        next = it.next()
        if(!next.done){
            console.log(next.value);
            
        }
    }
}

gen(array) 

// { name: '111', age: 11 }
// { name: '222', age: 22 }
// { name: '333', age: 33 }
// { name: '444', age: 44 }

gen(map)

// [ '111', 11 ]
// [ '222', 22 ]

gen(set)

// 1
// 2
// 3

我们平时开发中不会手动调用iterator 应为 他是有语法糖的就是for of 记住 for of 是不能循环对象的应为对象没有 iterator

for(let value of map){
    console.log(value); 
}

// [ '111', 11 ]
// [ '222', 22 ]

数组解构的原理其实也是调用迭代器的

let [a11,b11,c11] = [1,2,3]
console.log(a11,b11,c11);  // 1 2 3
let a111 = [...[1,2,3]]
console.log(a111);  // [ 1, 2, 3 ]
  • 以下为这些symbols的列表:
  • 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作用域排除在外。