likes
comments
collection
share

从零开始的 TypeScript 学习(三)TypeScript 对象的类型,真的是有点东西,从空对象声明,到对象类型的

作者站长头像
站长
· 阅读数 16

对象的类型

在 JavaScript 中,数据类型分为两种:

  • 原始类型NullUndefinedBooleanStringNumberBigInt Symbol
  • 引用类型Object(Array、Function)

在 TypeScript 中,声明一个对象类型有三种对象类型的写法:objectObject{}

object类型

小写的 object 表示:在 Javascript 可以用字面量描述的对象,它属于非原始类型(undefined, null,boolean,number, bigint, string, symbol),只包含对象、数组、函数;

const obj: object = {
    a: 123,
    b: 456,
    c: 789
}
const arr: object = [1, 2, 3]
const d: object = new Date()
const e: object = new Error()
const r: object = new RegExp('^', 'g')
const range: object = new Range()
const s: object = new Selection()
let m: object = new Map()
const f: object = ()=>{ }

上面示例中,只包含了对象、数组、函数,如果包含原始值,则报错:Type 'xxx' is not assignable to type 'object'.

const num: object = 123         // 报错
const str: object = "123"       // 报错
const bool: object = true       // 报错
const nul: object = null        //报错
const und: object = undefined   // 报错

Object类型

大写的 Object 类型代表 JavaScript 语言里面的广义对象。所有可以转成对象的值,都是Object类型,这囊括了几乎所有的值——不包括 nullundefined

const obj: Object = {
    a: 123,
}
const arr: Object = [1, 2, 3]
const m: Object = new Map()
const f: Object = ()=>{ }
const str: Object = "123"
const num: Object = 123
const bool: Object = true

上面示例中,原始类型值、对象、数组、函数都是合法的Object类型。

大多数时候,使用对象类型,只希望包含真正的对象,不希望包含原始类型。建议总是使用小写类型object,不使用大写类型Object

空对象 {} 类型

它同 Object 一样,可以看作是 Object 类型的简写形式,所以在开发中想使用 Object 类型时,常用空对象类型代替。空对象 {} 类型声明的变量同样不能初始化值为 nullundefined

const obj: {} = {
    a: 123,
}
const arr: {} = [1, 2, 3]
const m: {} = new Map()
const f: {} = ()=>{ }
const str: {} = "123"
const num: {} = 123
const bool: {} = true

一些特性

null

虽然 typeof null 的结果是 object ,但身为原始值的 null 是不能定义为 objectObject{} 中任意一种类型的,如果想定义相关类型的值为 null的变量,可以使用 object.create(),并且也可以用 object.create() 创建其他任意对象 。

const nul1: object = Object.create(null)
const nul2: Object = Object.create(null)
const nul3: {} = Object.create(null)
const obj1: object = Object.create({})
const obj2: Object = Object.create({})
const obj3: {} = Object.create({})

是空对象?

在 JavaScript 中,定义一个二维坐标点的对象,是很简单的:

const point = {
    x: 100,
    y: 200,
}
point.x;
point.y;

但在 TypeScript 中,定义了一个 Objectobject{} 类型的变量,对变量的每一个读取自定义属性的语句都会报错,但是对象内置属性方法就不会:

const point: object = {
    x: 100,
    y: 200,
}
point.x             // 报错
point.x = 101;      // 报错
point.y = 202;      // 报错
point.toString()    // "[object Object]"

上面示例中,无论是赋值还是读取值都会报错:property 'xxx' does not exist on type 'object' ,表示 object 类型上没有 xxx 属性。

无论是大写的 Object 类型,小写的 object 类型,还是简写的 {} 类型,都只包含 JavaScript 内置对象原生的属性和方法,自定义的属性和方法都不存在于这三个类型之中。即对象类型的定义中,只是定义了一个空对象类型,虽然初始化时可以为其赋值不同的属性和方法,但是在使用时,只能访问到内置对象和方法。

不为空的对象类型

那么如何声明一个不为空的对象?使用大括号表示对象,在大括号内部声明每个属性和方法的类型。属性的类型可以是分号结尾也可以是逗号结尾

const point: { x: number, y: number } = {       // 逗号分隔
    x: 100,
    y: 200,
}
const person: { name: string; age: number } = { // 分号分隔
    name: "Rick",
    age: 70,
}

上面示例中,声明了一个 point 对象,这个对象拥有两个属性值 xy 他们的类型分别为 numbernumber

一旦声明了对象类型,对象在赋值时,就不能缺少指定的属性,也不能有多余的属性;

let person: { name: string; age: number }
person = {  
    name: "Rick",
}   // 报错  少了必要属性
person = {
    name: "Rick",
    age: 70,
    sex: "男"
}   // 报错 多了必要属性

不能读写不存在的属性;

let person: { name: string; age: number } = {
    name: "Rick",
    age: 70,
}
person.grandson         // 报错 读取不存在的属性
person.sex = "男"        // 报错 写入不存在的属性

不能删除存在的属性;

let person: { name: string; age: number } = {
    name: "Rick",
    age: 70,
}
delete person.name

描述对象中的函数,只需要描述函数参数类型和返回值类型,不需要声明函数体,也不需要用空的函数体占位:

let obj: {
    x: number,
    y: number,
    add(x: number, y: number): number,
}
obj = {
    x: 1,
    y: 2,
    add(x: number,y: number): number{
        return this.x + this.y;
    }
}

可选属性

在 Typescript 中创建了对象之后就必须按照相应的对象类型进行赋值和操作。在某些情况下,开发者希望对象的某些属性是可选的,即可以存在也可以不存在。如果想定义一个可选的属性,只需要在属性名后面加一个问号。

let person: {
    name: string,
    age?: number,
}
person = {
    name: "Rick"
}

上面的示例中,属性 age 就是可选的。

let person: {
    name: string,
    age?: number,
} = {
    name: "Rick",
}
person.age.toPrecision();   // 执行报错

上面示例中,没有对 person.age 进行类型断言或类型收缩,它的类型可能是 undefined ,在调用 number 原型上的一些方法时,代码就会报错。如果开启 strictNullChecks 编译项,即使在变量的声明过程中属性有具体的非空值,代码也会在编译过程中也会提示报错。

// 开启 strictNullChecks 编译项
let person: {
    name: string,
    age?: number,
} = {
    name: "Rick",
    age: 70
}
person.age.toPrecision();   // 编译报错

如果想避免报错的发生(编译或运行),就需要类型断言或类型收缩:

let person: {
    name: string,
    age?: number,
} = {
    name: "Rick",
    age: 70
}
if (person.age) {
    person.age.toPrecision();
}
(person.age as number).toPrecision();
(<number>person.age).toPrecision()

上面示例中的代码都是可以正常执行的,需要注意的是在适当的地方添加小括号。

只读属性

属性名前面加上 readonly 关键字,表示这个属性是只读属性,不能修改。

let person: {
    readonly name: string,
    age:number
} = {
    name:"Rick",
    age:70
}
person.name = 'Morty'   // 报错不能修改
person.age = 17         // 可以修改

只读属性第一次初始化后,就不可以再进行修改了,结合可选属性也是如此:

let person: {
    readonly name?: string,
    age:number
} = {
    age:70
}
person.name = "123"

但是可以修改整个对象,在不破坏之前的类型规则前提下,把整个对象改掉:

let obj: { readonly foo: string, readonly bar: string } = {
    foo: "foo",
    bar: "bar",
}
obj = {
    foo: "123",
    bar: "bar",
}

如果属性值是一个对象,readonly 修饰符并不禁止修改该对象的属性,只是禁止完全替换掉该对象,就如同 const

let person: {
    readonly job: {
        name: string,
        salary: number,
    }
} = {
    job: {
        name: "nothing",
        salary: 0,
    }
}
person.job.name = '送外卖';
person.job.salary = 20000;
person.job = {
    name: "nothing",
    salary: 0,
}      // 不允许

上面示例中,person 中的 job 属性是只读的属性,由于它是一个对象,所以替换掉对象会报错,但是修改 job 对象中的属性值不会提示报错。

如果一个对象有两个引用,即两个变量对应同一个对象,其中一个变量是可写的,另一个变量是只读的,那么从可写变量修改属性,会影响到只读变量。

let w: {
    name: string;
    age: number;
} = {
    name: "Vicky",
    age: 42,
};

let r: {
    readonly name: string;
    readonly age: number;
} = w;

w.age += 1;
r.age; // 43

Typescript 编译器会检查 readonly 变量的赋值操作,并在检测到任何尝试修改其值的操作时报告错误。上面示例中,Typescript 编译器会检查 r.age 的赋值操作,而不会检查 w.age 的赋值操作; r.agew.age 两个引用指向了相同的对象,所以在修改 w.age 的变量值时也会影响到 r.age 的值。

as const

as const 字面意思就是断言为常量。正常情况下,编译器会对没有进行类型声明的变量进行类型断言,会将变量断言为一个比较广泛的基础类型或对象类型,但是使用 const 断言会告诉编译器为变量推断出它能推断出的最窄或最特定的类型。

let a = 123;            // let a: number
let b = 234 as const;   // let b: 234

上面示例中,因为没有指定变量 ab 的类型;Typescript 会对变量进行类型断言。正常情况下,变量 a 会被推断成了 number 类型;在变量 b 的声明后加上 as const,Typescript 在断言过程中就会推断为最特定的类型,也就是一个字面量类型 234。字面量类型简单理解就是,字面量作为类型,只能赋值为字面量本身的值。

as const 只能应用于对枚举成员字符串数字布尔值数组对象字面值的引用

// 可以使用 as const
let a = 123 as const;
let b = "123" as const;
let c = true as const;

let obj = {
    a: 123,
    b: " 123"
} as const
let arr = [1, 2] as const
enum Color {            // 枚举(后面的文章讲讲)
  Red, // 0
  Green, // 1
  Blue, // 2
} 
Color[0] as const       // 枚举成员

// 不能使用 as const
let d = BigInt(123n) as const
let e = null as const
let f = undefined as const
let g = new Date() as const 
let h = Symbol("hell") as const
let fun = function (a: number, b: number) {
    return a + b
} as const 

上面示例中,as const 只对字符串,数字,布尔值等进行了断言,其他的类型都会报错。

as const 在对象的断言中,不仅会断言为最窄的类型,还会将对象的属性和方法加上只读属性。完全将对象视为一个"静态对象"。

let obj = {
    foo: "foo",
    bar: "bar"
} as const   
/*  
let obj: {
    readonly foo: "foo";
    readonly bar: "bar";
} 
*/
obj.foo = "123"     // 报错

对于数组,也同样适用:

let arr = [1, 2, 3] as const
arr[0] = 100    // 报错

只有需要断言的时候,as const 才会生效,如果变量明确地声明了类型,那么 TypeScript 会以声明的类型为准。

let obj: { foo: string, bar: string } = {
    foo: "foo",
    bar: "bar",
} as const;
obj.foo = "123";
obj.bar = "234";
obj = {
    foo:"1234",
    bar:"123123"
}

上面示例中,类型已经声明了,as const 也就不会生效,其中的属性值也可以进行修改。

本章最后

内容上如果有任何问题和错误,希望大佬们可以指出,我会及时修改并虚心求教。

转载自:https://juejin.cn/post/7378014138137739315
评论
请登录