从零开始的 TypeScript 学习(三)TypeScript 对象的类型,真的是有点东西,从空对象声明,到对象类型的
对象的类型
在 JavaScript 中,数据类型分为两种:
- 原始类型:
Null
、Undefined
、Boolean
、String
、Number
、BigInt
和Symbol
。 - 引用类型:
Object(Array、Function)
。
在 TypeScript 中,声明一个对象类型有三种对象类型的写法:object
、Object
、{}
。
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
类型,这囊括了几乎所有的值——不包括 null
和 undefined
。
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
类型时,常用空对象类型代替。空对象 {}
类型声明的变量同样不能初始化值为 null
和 undefined
。
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
是不能定义为 object
、Object
、 {}
中任意一种类型的,如果想定义相关类型的值为 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 中,定义了一个 Object
或 object
、{}
类型的变量,对变量的每一个读取自定义属性的语句都会报错,但是对象内置属性方法就不会:
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
对象,这个对象拥有两个属性值 x
和 y
他们的类型分别为 number
和 number
。
一旦声明了对象类型,对象在赋值时,就不能缺少指定的属性,也不能有多余的属性;
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.age
与 w.age
两个引用指向了相同的对象,所以在修改 w.age
的变量值时也会影响到 r.age
的值。
as const
as const
字面意思就是断言为常量。正常情况下,编译器会对没有进行类型声明的变量进行类型断言,会将变量断言为一个比较广泛的基础类型或对象类型,但是使用 const
断言会告诉编译器为变量推断出它能推断出的最窄或最特定的类型。
let a = 123; // let a: number
let b = 234 as const; // let b: 234
上面示例中,因为没有指定变量 a
和 b
的类型;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