JavaScript 中的对象字面量

对象字面量) (Object literals)是 JavaScript 中的一个生成对象的功能。
对象字面值是封闭在花括号对({})中的一个对象的零个或多个”属性名-值”对的(元素)列表。
我们日常使用的是 ES 2015 开始引入的增强的对象字面量) (Enhanced Object literals)。
下面是一个例子:

  1. const obj = {
  2. 0x1: 'foo',
  3. bar: 114514,
  4. "baz": [1, 2, {}],
  5. // __proto_
  6. __proto__: theProtoObj,
  7. // Shorthand for ‘handler: handler’
  8. handler,
  9. // Methods
  10. toString() {
  11. // Super calls
  12. return "d " + super.toString();
  13. },
  14. // Property of arrow function
  15. arrowFunctionProperty: () => 'arrowFunctionProperty',
  16. // Computed (dynamic) property names
  17. [ 'prop_' + (() => 42)() ]: 42,
  18. // Computed property names of Symbol
  19. [Symbol.iterator]: '123',
  20. };

我就不赘述语法了。就简单提一下,key 是 0x1 还是 0b1 还是 1,都相当于是 “1”。
key 可以是标识符(bar,arrowFunctionProperty, handler),字符串(”baz”),数字(0x1),方括号括起来的计算出的值。Symbol 只可以放在方括号中括起来。

类型字面量

请注意一下,这里是说类型字面量(Type Literal),不是说字面量类型(Literal Type),这个名词的出处是 TypeScript 的源码,文档和很多教程里貌似都没有提这个名词,除了我在 tslint 中看到的这条规则

毕竟本文本质上就是文档的搬运工,所以我提一下,在 TypeScript 中对应的文档是 Type Alias。文档中用 object literal type 来表示 type literal。

与类型字面量对应的就是 js 中的对象字面量。所以他们的语法很相似。

  1. type Handler = Function;
  2. type Foo = {
  3. baz: [
  4. 1,
  5. number,
  6. {
  7. y: Array<string>;
  8. }
  9. ];
  10. readonly handler: Handler;
  11. toString(): string;
  12. readonly [Symbol.iterator]: '123';
  13. 0x1: 'foo';
  14. "bar": number;
  15. };

我们可以把一对键值对叫做 Signature,一个 type literal 由花括号和 signature 列表组成,上面的例子中实际上由两种 signature:

  • 一种是 toString 这种,叫做 method signature,与 js 中的 object literal 中的 method。它不能设 readonly 作为 modifier,它只能是可读可写的。
  • 上面例子中,其余的都是 property signature,property signature 中的 key 部分,也可以是字符串和数字的字面量,标识符(baz, handler),也可以是 computed property name,比如 [Symbol.iterator]。它可以设置 readonly ,默认是可读可写的。

上面的每个 signature 结尾都带了一个 ; ,也可以选择使用 , ,也可以忽略。我一般是忽略不写,然后让 prettier 帮我补上,至于补上什么不重要,保持一致就好。

对于标识符是 computed property name 的情况,提两个小要点:

  • 方括号内的部分,应该填写值空间的变量,你不能填写类型空间的内容。
  • 对于类型字面量的方括号中间,我们只允许填写类型是 unique symbol 的值。

    可索引类型

ES 2015 引入了 Map,所以我们需要实现 Map(有的地方也叫做 Dictionary)有了比较好的做法。在此之前,我们一般用 Object 当作 Map 使用。Object 中的键值对,键只能是 string 和 symbol。取得值时候,通过 obj[key] 来取。如果输入的key不是字符串或者 symbol,会被强制转型为字符串再继续操作。

可索引类型(Indexable Types) 是描述把 Object 当作 Dictionary 使用的时候的类型。

  1. type Animal = {
  2. name: string
  3. age: number
  4. }
  5. type Foo = {
  6. [key: string]: Animal;
  7. }
  8. const a: Foo = {};
  9. a['foo'] = {
  10. name: 'foo',
  11. age: 2,
  12. };
  13. type ReadonlyFoo = {
  14. readonly [key: string]: Animal;
  15. };
  16. const b: ReadonlyFoo = {
  17. foo:{
  18. name: 'foo',
  19. age: 2,
  20. }
  21. }
  22. const foo = b.foo // Animal
  23. b['bar'] = foo; // Error: Index signature in type 'ReadonlyFoo' only permits reading

上面就是两个使用 indexable type 的例子。方括号内是一对冒号分割的内容。

方括号内冒号左边的是任意写的标识符,除了视觉效果,貌似没其他什么特定作用。我们常用的一般是 key 或者 index。当然你可以起成其他奇奇怪怪的标识符, foo,bar 啥的都可以。

方括号内冒号右边的是一个类型,叫做 index signature parameter type,而且 index signature parameter type 只能是类型关键字 number 或者类型关键字 string,一个字母都不能差,你填写一个实际值是 number/string 的 type alias,填写一个字面量类型,填写一个 bigint 或者填写其他任何对类型的计算都不允许。

方括号的冒号后面的 Animal 叫做 index type,可以是任意类型,没有任何限制。如果 index signature parameter type 是 number 时候, index type 可以被叫做 numeric index type;如果 index signature parameter type 是 string 时候, index type 可以被叫做 string index type。

整个类型,除去最外面的大括号,也就是 [key: string]: Animal; 叫做 index signature。

{ [key: string]: Animal } 也是一个 type literal。这也就暗示了,index signature 可以和 method signature 与 property signature 一起出现。

下面看一个例子:

  1. type NumberDictionary = {
  2. [index: string]: number;
  3. length: 123; // 可以,length是number类型的子类型
  4. [index: number]: string; // Error: Numeric index type 'string' is not assignable to string index type 'number'
  5. name: string // Error: Property 'name' of type 'string' is not assignable to string index type 'number'.
  6. }
  7. type NumericIndex = {
  8. [index: number]: string;
  9. length: 123;
  10. '1.1': 123; // Error: Property '"1.1"' of type '123' is not assignable to numeric index type 'string'
  11. 2333: '2333';
  12. '0b11': 123;
  13. 0b11: '2333';
  14. name: string;
  15. };

如果一个 type literal 既有 numeric index signature 又有 string index signature 时候,我们要保证 numeric index type 是 string index type 的子类型。
如果一个 type literal 存在 string index signature 和 其他 signature(method signature,property signature) 时候,我们要保证其他的 signature 中的 type 是 string index type 的子类型。
如果一个 type literal 存在 numeric index signature 和 其他 signature(method signature,property signature) 时候,我们找出其他的 key 是数字对应的字符串的 signature,比如 key 是 "1.1"2333 或者 0b11 的 signature, 这些 signature 的 type 是 numeric index type 的子类型。

例子


下面会出现一些没介绍过的,以后会介绍的语法。
这个语法的设计是为了如下的用途,举个例子,我们能知道一个对象的部分信息,比如我们使用一个对象表示环境变量。那么我们会写成:

  1. type ProcessEnv = {
  2. [key: string]: string | undefined;
  3. }

上面的 | 是 union type,意思就是要么是 string 类型,要么是 undefined 类型。

如果我们知道部分信息,比如我们知道环境变量 PUBLIC_URL 肯定存在,则我们可以写成

  1. type ProcessEnv = {
  2. [key: string]: string | undefined;
  3. PUBLIC_URL: string;
  4. }


如果我们知道更多的信息,比如我们知道某些环境变量只可能是某些字段,我们会写成下面的样子

  1. type ProcessEnv = {
  2. [key: string]: string | undefined;
  3. PUBLIC_URL: string;
  4. NODE_ENV: 'development' | 'production' | 'test';
  5. }