Skip to main content

对象

在 JavaScript 中可以通过多种不同的方式使用对象。有多种方法可以输入它们以支持不同的用例。

¥Objects can be used in many different ways in JavaScript. There are a number of ways to type them in order to support the different use cases.

  • 确切的对象类型:具有一组属性的对象,例如 {a: number}。我们建议使用精确的对象类型而不是不精确的对象类型,因为它们更精确并且与其他类型系统功能(例如 展开)交互更好。

    ¥Exact object types: An object which has exactly a set of properties, e.g. {a: number}. We recommend using exact object types rather than inexact ones, as they are more precise and interact better with other type system features, like spreads.

  • 不精确的对象类型:一个对象至少具有一组属性,但可能具有其他未知属性,例如 {a: number, ...}

    ¥Inexact object types: An object with at least a set of properties, but potentially other, unknown ones, e.g. {a: number, ...}.

  • 带有索引器的对象:可以用作从键类型到值类型的映射的对象,例如 {[string]: boolean}

    ¥Objects with indexers: An object that can used as a map from a key type to a value type, e.g. {[string]: boolean}.

  • 接口:接口与对象类型是分开的。只有它们才能描述类的实例。例如。interfaces {a: number}

    ¥Interfaces: Interfaces are separate from object types. Only they can describe instances of classes. E.g. interfaces {a: number}.

对象类型尝试尽可能匹配 JavaScript 中对象的语法。使用大括号 {} 和名称-值对,使用冒号 :(以逗号 , 分隔)。

¥Object types try to match the syntax for objects in JavaScript as much as possible. Using curly braces {} and name-value pairs using a colon : split by commas ,.

1const obj1: {foo: boolean} = {foo: true};2const obj2: {3  foo: number,4  bar: boolean,5  baz: string,6} = {7  foo: 1,8  bar: true,9  baz: 'three',10};

可选对象类型属性

¥Optional object type properties

在 JavaScript 中,访问不存在的属性的计算结果为 undefined。这是 JavaScript 程序中常见的错误来源,因此 Flow 将这些错误转化为类型错误。

¥In JavaScript, accessing a property that doesn't exist evaluates to undefined. This is a common source of errors in JavaScript programs, so Flow turns these into type errors.

1const obj = {foo: "bar"};2obj.bar; // Error!

如果你的对象有时没有属性,则可以通过在对象类型中的属性名称后面添加问号 ? 将其设为可选属性。

¥If you have an object that sometimes does not have a property you can make it an optional property by adding a question mark ? after the property name in the object type.

1const obj: {foo?: boolean} = {};2
3obj.foo = true;    // Works!4obj.foo = 'hello'; // Error!

除了它们的设置值类型之外,这些可选属性可以是 void 或完全省略。然而,它们不可能是 null

¥In addition to their set value type, these optional properties can either be void or omitted altogether. However, they cannot be null.

1function acceptsObject(value: {foo?: string}) { /* ... */ }2
3acceptsObject({foo: "bar"});     // Works!4acceptsObject({foo: undefined}); // Works!5acceptsObject({});               // Works!6
7acceptsObject({foo: null});      // Error!

要使对象类型中的所有属性都可选,你可以使用 Partial 工具类型:

¥To make all properties in an object type optional, you can use the Partial utility type:

1type Obj = {2  foo: string,3};4
5type PartialObj = Partial<Obj>; // Same as `{foo?: string}`

要使对象类型中的所有属性成为必需,你可以使用 Required 工具类型:

¥To make all properties in an object type required, you can use the Required utility type:

1type PartialObj = {2  foo?: string,3};4
5type Obj = Required<PartialObj>; // Same as `{foo: string}`

只读对象属性

¥Read-only object properties

你可以将 方差 注释添加到对象属性中。

¥You can add variance annotations to your object properties.

要将属性标记为只读,你可以使用 +

¥To mark a property as read-only, you can use the +:

1type Obj = {2  +foo: string,3};4
5function func(o: Obj) {6  const x: string = o.foo; // Works!7  o.foo = 'hi'; // Error!8}

要使对象类型中的所有对象属性变为只读,你可以使用 $ReadOnly 工具类型:

¥To make all object properties in an object type read-only, you can use the $ReadOnly utility type:

1type Obj = {2  foo: string,3};4
5type ReadOnlyObj = $ReadOnly<Obj>; // Same as `{+foo: string}`

你还可以使用 - 将属性标记为只写:

¥You can also mark your properties as write-only with -:

1type Obj = {2  -foo: string,3};4
5function func(o: Obj) {6  const x: string = o.foo; // Error!7  o.foo = 'hi'; // Works!8}

对象方法

¥Object methods

对象中的方法语法与函数属性具有相同的运行时行为。这两个对象在运行时是等效的:

¥Method syntax in objects has the same runtime behavior as a function property. These two objects are equivalent at runtime:

1const a = {2  foo: function () { return 3; }3};4const b = {5  foo() { return 3; }6}

然而,尽管它们的运行时行为相同,Flow 对它们的检查略有不同。特别是用方法语法写的对象属性是 只读;Flow 不允许你向它们写入新值。

¥However, despite their equivalent runtime behavior, Flow checks them slightly differently. In particular, object properties written with method syntax are read-only; Flow will not allow you to write a new value to them.

1const b = {2  foo() { return 3; }3}4b.foo = () => { return 2; } // Error!

此外,对象方法不允许在其主体中使用 this,以保证其 this 参数的简单行为。更喜欢通过名称引用对象,而不是使用 this

¥Additionally, object methods do not allow the use of this in their bodies, in order to guarantee simple behavior for their this parameters. Prefer to reference the object by name instead of using this.

1const a = {2  x: 3,3  foo() { return this.x; } // Error!4}5const b = {6  x: 3,7  foo(): number { return b.x; } // Works!8}

对象类型推断

¥Object type inference

注意:空对象字面量的行为在版本 0.191 中已更改 - 有关更多详细信息,请参阅此 博客文章

¥NOTE: The behavior of empty object literals has changed in version 0.191 - see this blog post for more details.

当你创建对象值时,其类型在创建点设置。你无法添加新属性或修改现有属性的类型。

¥When you create an object value, its type is set at the creation point. You cannot add new properties, or modify the type of existing properties.

1const obj = {2  foo: 1,3  bar: true,4};5
6const n: number  = obj.foo; // Works!7const b: boolean = obj.bar; // Works!8
9obj.UNKNOWN; // Error - prop `UNKNOWN` is not in the object value10obj.foo = true; // Error - `foo` is of type `number`

如果你提供类型注释,则可以将对象值中缺少的属性添加为可选属性:

¥If you supply a type annotation, you can add properties missing in the object value as optional properties:

1const obj: {2  foo?: number,3  bar: boolean,4} = {5  // `foo` is not set here6  bar: true,7};8
9const n: number | void = obj.foo; // Works!10const b: boolean = obj.bar; // Works!11
12if (b) {13  obj.foo = 3; // Works!14}

你还可以为特定属性提供更广泛的类型:

¥You can also give a wider type for a particular property:

1const obj: {2  foo: number | string,3} = {4  foo: 1,5};6
7const foo: number | string = obj.foo; // Works!8obj.foo = "hi"; // Works!

如果你提供适当的类型注释,则空对象可以解释为 字典

¥The empty object can be interpreted as a dictionary, if you supply the appropriate type annotation:

1const dict: {[string]: number} = {}; // Works!

如果对象字面量递归引用自身(超出简单情况),你可能需要向对象字面量添加类型注释:

¥You may need to add type annotations to an object literal, if it references itself recursively (beyond simple cases):

1const Utils = { // Error2  foo() {3    return Utils.bar();4  },5  bar() {6    return 1;7  }8};9
10const FixedUtils = { // Works!11  foo(): number {12    return FixedUtils.bar();13  },14  bar(): number {15    return 1;16  }17};

精确和不精确的对象类型

¥Exact and inexact object types

精确的对象类型是默认值(从版本 0.202 开始),除非你在 .flowconfig 中设置了 exact_by_default=false

¥Exact object types are the default (as of version 0.202), unless you have set exact_by_default=false in your .flowconfig.

不精确的对象(用 ... 表示)允许传入额外的属性:

¥Inexact objects (denoted with the ...) allow extra properties to be passed in:

1function method(obj: {foo: string, ...}) { /* ... */ }2
3method({foo: "test", bar: 42}); // Works!

注意:这是因为 "宽度子类型"

¥Note: This is because of "width subtyping".

但确切的对象类型不会:

¥But exact object types do not:

1function method(obj: {foo: string}) { /* ... */ }2
3method({foo: "test", bar: 42}); // Error!

如果设置了 exact_by_default=false,则可以通过在大括号内部添加一对 "竖条" 或 "管道" 来表示确切的对象类型:

¥If you have set exact_by_default=false, you can denote exact object types by adding a pair of "vertical bars" or "pipes" to the inside of the curly braces:

1const x: {|foo: string|} = {foo: "Hello", bar: "World!"}; // Error!

交集 的确切对象类型可能无法按你的预期工作。如果需要组合精确的对象类型,请使用 对象类型传播

¥Intersections of exact object types may not work as you expect. If you need to combine exact object types, use object type spread:

1type FooT = {foo: string};2type BarT = {bar: number};3
4type FooBarT = {...FooT, ...BarT};5const fooBar: FooBarT = {foo: '123', bar: 12}; // Works!6
7type FooBarFailT = FooT & BarT;8const fooBarFail: FooBarFailT = {foo: '123', bar: 12}; // Error!

对象类型传播

¥Object type spread

就像可以传播对象值一样,你也可以传播对象类型:

¥Just like you can spread object values, you can also spread object types:

1type ObjA = {2  a: number,3  b: string,4};5
6const x: ObjA = {a: 1, b: "hi"};7
8type ObjB = {9  ...ObjA,10  c: boolean,11};12
13const y: ObjB = {a: 1, b: 'hi', c: true}; // Works!14const z: ObjB = {...x, c: true}; // Works!

你必须小心散布不精确的对象。生成的对象也必须是不精确的,并且传播的不精确对象可能具有未知的属性,这些属性可以以未知的方式覆盖先前的属性:

¥You have to be careful spreading inexact objects. The resulting object must also be inexact, and the spread inexact object may have unknown properties that can override previous properties in unknown ways:

1type Inexact = {2  a: number,3  b: string,4  ...5};6
7type ObjB = { // Error!8  c: boolean,9  ...Inexact,10};11
12const x: ObjB = {a:1, b: 'hi', c: true};

具有 索引器 的对象也存在同样的问题,因为它们也有未知的键:

¥The same issue exists with objects with indexers, as they also have unknown keys:

1type Dict = {2  [string]: number,3};4
5type ObjB = { // Error!6  c: boolean,7  ...Dict,8};9
10const x: ObjB = {a: 1, b: 2, c: true};

在运行时传播对象值只会传播 "自有的" 属性,即直接位于对象上的属性,而不是原型链上的属性。对象类型传播的工作原理相同。因此,你无法传播 接口,因为它们不跟踪属性是否为 "自有的":

¥Spreading an object value at runtime only spreads "own" properties, that is properties that are on the object directly, not the prototype chain. Object type spread works in the same way. Because of this, you can't spread interfaces, as they don't track whether a property is "own" or not:

1interface Iface {2  a: number;3  b: string;4}5
6type ObjB = { // Error!7  c: boolean,8  ...Iface,9};10
11const x: ObjB = {a: 1, b: 'hi', c: true};

对象作为映射

¥Objects as maps

JavaScript 包含 Map 类,但使用对象作为映射仍然很常见。在此用例中,对象可能会在其生命周期中添加和检索属性。此外,属性键甚至可能不是静态已知的,因此不可能写出类型注释。

¥JavaScript includes a Map class, but it is still very common to use objects as maps as well. In this use case, an object will likely have properties added to it and retrieved throughout its lifecycle. Furthermore, the property keys may not even be known statically, so writing out a type annotation would not be possible.

对于此类对象,Flow 提供了一种特殊的属性,称为 "索引器属性。"。索引器属性允许使用与索引器键类型匹配的任何键进行读取和写入。

¥For objects like these, Flow provides a special kind of property, called an "indexer property." An indexer property allows reads and writes using any key that matches the indexer key type.

1const o: {[string]: number} = {};2o["foo"] = 0;3o["bar"] = 1;4const foo: number = o["foo"];

出于文档目的,可以选择命名索引器:

¥An indexer can be optionally named, for documentation purposes:

1const obj: {[user_id: number]: string} = {};2obj[1] = "Julia";3obj[2] = "Camille";4obj[3] = "Justin";5obj[4] = "Mark";

当对象类型具有索引器属性时,即使该对象在运行时该槽中没有值,也会假定属性访问具有带注释的类型。与数组一样,程序员有责任确保访问安全。

¥When an object type has an indexer property, property accesses are assumed to have the annotated type, even if the object does not have a value in that slot at runtime. It is the programmer's responsibility to ensure the access is safe, as with arrays.

1const obj: {[number]: string} = {};2obj[42].length; // No type error, but will throw at runtime

索引器属性可以与命名属性混合使用:

¥Indexer properties can be mixed with named properties:

1const obj: {2  size: number,3  [id: number]: string4} = {5  size: 06};7
8function add(id: number, name: string) {9  obj[id] = name;10  obj.size++;11}

你可以使用 方差 注释将索引器属性标记为只读(或只写):

¥You can mark an indexer property as read-only (or write-only) using variance annotations:

1type ReadOnly = {+[string]: number};2type WriteOnly = {-[string]: number};

键、值和索引访问

¥Keys, values, and indexed access

你可以使用 $Keys 工具类型提取对象类型的键:

¥You can extract the keys of an object type using the $Keys utility type:

1type Obj = {2  foo: string,3  bar: number,4};5
6type T = $Keys<Obj>;7
8function acceptsKeys(k: T) { /* ... */ }9
10acceptsKeys('foo'); // Works!11acceptsKeys('bar'); // Works!12acceptsKeys('hi'); // Error!

你可以使用 $Values 工具类型提取对象类型的值:

¥You can extract the values of an object type using the $Values utility type:

1type Obj = {2  foo: string,3  bar: number,4};5
6type T = $Values<Obj>;7
8function acceptsValues(v: T) { /* ... */ }9
10acceptsValues(2); // Works!11acceptsValues('hi'); // Works!12acceptsValues(true); // Error!

你可以使用 索引访问类型 获取对象类型的特定属性的类型:

¥You can get the type of an object type's specific property using indexed access types:

1type Obj = {2  foo: string,3  bar: number,4};5
6type T = Obj['foo'];7
8function acceptsStr(x: T) { /* ... */ }9
10acceptsStr('hi'); // Works!11acceptsStr(1); // Error!

任意对象

¥Arbitrary objects

如果你想安全地接受任意对象,可以使用几种模式。

¥If you want to accept an arbitrary object safely, there are a couple of patterns you could use.

空的不精确对象 {...} 接受任何对象:

¥An empty inexact object {...} accepts any object:

1function func(obj: {...}) {2  // ...3}4
5func({}); // Works!6func({a: 1, b: "foo"}); // Works!

对于有界的 泛型 来说,接受任何对象通常是正确的选择:

¥It's often the right choice for a generic bounded to accept any object:

1function func<T: {...}>(obj: T) {2  // ...3}4
5func({}); // Works!6func({a: 1, b: "foo"}); // Works!

但是,你无法访问 {...} 之外的任何属性。

¥However, you can't access any properties off of {...}.

你还可以尝试使用带有 mixed 值的 字典,这将允许你访问任何属性(带有生成的 mixed 类型):

¥You can also try using a dictionary with mixed values, which would allow you to access any property (with a resulting mixed type):

1function func(obj: {+[string]: mixed}) {2  const x: mixed = obj['bar'];3}4
5func({}); // Works!6func({a: 1, b: "foo"}); // Works!

类型 Object 只是 any 的别名,并且是不安全的。你可以使用 不明类型棉绒 禁止在代码中使用它。

¥The type Object is just an alias for any, and is unsafe. You can ban its use in your code with the unclear-type lint.