TypeScript-对象类型

2023-04-06  本文已影响0人  奋斗_登

在JavaScript中存在这样一种说法,那就是“一切皆为对象”。有这种说法是因为JavaScript中的绝大多数值都可以使用对象来表示。例如,函数、数组和对象字面量等本质上都是对象。对于原始数据类型,如String类型,JavaScript也提供了相应的构造函数来创建能够表示原始值的对象。例如,下例中使用内置的String构造函数创建了一个表示字符串的对象,示例如下:

const hi = new String('hi');

在某些操作中,原始值还会自动地执行封箱操作,将原始数据类型转换为对象数据类型。例如,在字符串字面量上直接调用内置的“toUpperCase()”方法时,JavaScript会先将字符串字面量转换为对象类型,然后再调用字符串对象上的“toUpperCase()”方法。示例如下:

// 自动封箱,将'hi'转换为String对象类型
'hi'.toUpperCase();

// 自动封箱,将3转换为Number对象类型
// 注意:这里使用了两个点符号,第一个点实际上是一个小数点,只需让JavaScript编译器知道第二个点想要调用属性或方
3..toString()

数组、元组、函数、接口等都属于对象类型,TypeScript提供了多种定义对象的类型,以下介绍下三种基本的对象类型

1. Object

这里的Object指的是Object类型,而不是JavaScript内置的“Object()”构造函数。Object类型表示一种类型,而“Object()”构造函数则表示一个值。因为“Object()”构造函数是一个值,因此它也有自己的类型。但要注意的是,“Object()”构造函数的类型不是Object类型。为了更好地理解Object类型,让我们先了解一下“Object()”构造函数。
JavaScript提供了内置的“Object()”构造函数来创建一个对象。

const obj = new Object();

在实际代码中,使用“Object()”构造函数来创建对象的方式并不常用。在创建对象时,我们通常会选择使用更简洁的对象字面量。虽然不常使用“Object()”构造函数来创建对象,但是“Object()”构造函数提供了一些非常常用的静态方法,例如“Object.assign()”方法和“Object.create()”方法等。
接下来,让我们深入分析一下TypeScript源码中对“Object()”构造函数的类型定义。下面仅摘取一部分着重关注的类型定义(源文件:lib.es5.d.ts):

interface ObjectConstructor {
    new(value?: any): Object;
    (): any;
    (value: any): any;

    /** A reference to the prototype for a class of objects. */
    readonly prototype: Object;
   /**
     * Returns the prototype of an object.
     * @param o The object that references the prototype.
     */
    getPrototypeOf(o: any): any;

    /**
     * Gets the own property descriptor of the specified object.
     * An own property descriptor is one that is defined directly on the object and is not inherited from the object's prototype.
     * @param o Object that contains the property.
     * @param p Name of the property.
     */
    getOwnPropertyDescriptor(o: any, p: PropertyKey): PropertyDescriptor | undefined;

    /**
     * Returns the names of the own properties of an object. The own properties of an object are those that are defined directly
     * on that object, and are not inherited from the object's prototype. The properties of an object include both fields (objects) and functions.
     * @param o Object that contains the own properties.
     */
    getOwnPropertyNames(o: any): string[];

    /**
     * Creates an object that has the specified prototype or that has null prototype.
     * @param o Object to use as a prototype. May be null.
     */
    create(o: object | null): any;

   // ....省略了其他成员

}

/**
 * Provides functionality common to all JavaScript objects.
 */
declare var Object: ObjectConstructor;

由该定义能够直观地了解到“Object()”构造函数的类型是ObjectConstructor类型而不是Object类型,它们是不同的类型。代码中此行 readonly prototype: Object;,prototype属性的类型为Object类型。构造函数的prototype属性值决定了实例对象的原型。此外,“Object.prototype”是一个特殊的对象,它是JavaScript中的公共原型对象。也就是说,如果程序中没有刻意地修改一个对象的原型,那么该对象的原型链上就会有“Object.prototype”对象,因此也会继承“Object.prototype”对象上的属性和方法。
现在,我们来说说Object类型。Object类型是特殊对象“Object.prototype”的类型,该类型的主要作用是描述JavaScript中几乎所有对象都共享(通过原型继承)的属性和方法。Object类型的具体定义如下所示(TypeScript源码: lib/lib.es5.d.ts):

interface Object {
    /** The initial value of Object.prototype.constructor is the standard built-in Object constructor. */
    constructor: Function;

    /** Returns a string representation of an object. */
    toString(): string;

    /** Returns a date converted to a string using the current locale. */
    toLocaleString(): string;

    /** Returns the primitive value of the specified object. */
    valueOf(): Object;

    /**
     * Determines whether an object has a property with the specified name.
     * @param v A property name.
     */
    hasOwnProperty(v: PropertyKey): boolean;

    /**
     * Determines whether an object exists in another object's prototype chain.
     * @param v Another object whose prototype chain is to be checked.
     */
    isPrototypeOf(v: Object): boolean;

    /**
     * Determines whether a specified property is enumerable.
     * @param v A property name.
     */
    propertyIsEnumerable(v: PropertyKey): boolean;
}

通过该类型定义能够了解到,Object类型里定义的方法都是通用的对象方法,如“valueOf()”方法。

let obj: Object;

// 正确
obj = { x: 0 };
obj = true;
obj = 'hi';
obj = 1;

// 编译错误
obj = undefined;
obj = null;

对象能够赋值给Object类型是理所当然的,但为什么原始值也同样能够赋值给Object类型呢?实际上,这样设计正是为了遵循JavaScript语言的现有行为。我们在本章开篇处介绍了JavaScript语言中存在自动封箱操作。当在原始值上调用某个方法时,JavaScript会对原始值执行封箱操作,将其转换为对象类型,然后再调用相应方法。Object类型描述了所有对象共享的属性和方法,而JavaScript允许在原始值上直接访问这些方法,因此TypeScript允许将原始值赋值给Object类型。示例如下:

'str'.valueOf();

const str: Object = 'str';
str.valueOf();

注意以下写法是错误的

const point: Object = { x: 0, y: 0 };

此例中,将常量point的类型定义为Object类型。虽然该代码不会产生任何编译错误,但它是一个明显的使用错误。原因刚刚介绍过,Object类型的用途是描述“Object.prototype”对象的类型,即所有对象共享的属性和方法。在描述自定义对象类型时有很多更好的选择,完全不需要使用Object类型,例如接下来要介绍的object类型和对象字面量类型等。\color{red}{在TypeScript官方文档中也明确地指出了不应该使用Object类型,而是应该使用object类型来代替}

2. object

在TypeScript 2.2版本中,增加了一个新的object类型表示非原始类型。object类型使用object关键字作为标识,object类型名中的字母全部为小写。示例如下:

const point: object = { x: 0, y: 0 };

object类型的关注点在于类型的分类,它强调一个类型是非原始类型,即对象类型。object类型的关注点不是该对象类型具体包含了哪些属性,例如对象类型是否包含一个名为name的属性,因此,不允许读取和修改object类型上的自定义属性。示例如下:

const obj: object = { foo: 0 };
//: Property 'foo' does not exist on type 'object'.
obj.foo;

在object类型上仅允许访问对象的公共属性和方法,也就是Object类型中定义的属性和方法。示例如下:

const obj: object = { foo: 0 };
obj.valueOf();

只有非原始类型,也就是对象类型能够赋给object类型

let nonPrimitive: object;

// 正确
nonPrimitive = {};
nonPrimitive = { x: 0 };
nonPrimitive = [0];
nonPrimitive = new Date();
nonPrimitive = function () {};

object类型仅能够赋值给以下三种类型:

在JavaScript中,有一些内置方法只接受对象作为参数,我们前面提到的“Object.create()”方法,该方法的第一个参数必须传入对象或者null值作为新创建对象的原型。如果传入了原始类型的值,例如数字1,那么将产生运行时的类型错误。

 // 正确
const a = Object.create(Object.prototype);
const b = Object.create(null);
// 类型错误
const c = Object.create(1);
3. 对象类型字面量

对象类型字面量的语法与对象字面量的语法相似。在定义对象类型字面量时,需要将类型成员依次列出。对象类型字面量的语法如下所示:

{
    TypeMember;
    TypeMember;
    ...
}

常见的属性签名如下(Point对象类型包含两个属性签名类型成员,分别为表示横坐标的属性x和表示纵坐标的属性y,两者的类型均为number类型。):

let point: { x: number; y: number } = { x: 0, y: 0 };

属性签名中的属性名可以为可计算属性名,但需要该可计算属性名满足以下条件之一:

const a: 'a' = 'a';
const b: 0 = 0;
  
let obj: {
      [a]: boolean;
      [b]: boolean;
      ['c']: boolean;
      [1]: boolean;
  };
const s: unique symbol = Symbol();
let obj: {
     [s]: boolean;
};
let obj: {
     [Symbol.toStringTag]: string;
};
//可选属性
let point: { x: number; y: number; z?: number };
point = { x: 0, y: 0, z: 0 };

//只读属性
let point: {
    readonly x: number;
    readonly y: number;
 };
point = { x: 0, y: 0 };

空对象类型字面量“{}”与Object类型十分相似。而事实上也正是如此,单从行为上来看两者是可以互换使用的。例如,除了undefined值和null值外,其他任何值都可以赋值给空对象类型字面量“{}”和Object类型。同时,空对象类型字面量“{}”和Object类型之间也允许互相赋值。

let a: Object = 'hi';
let b: {} = 'hi';

a = b;
b = a;

两者的区别主要在于语义上。全局的Object类型用于描述对象公共的属性和方法,它相当于一种专用类型,因此程序中不应该将自定义变量、参数等类型直接声明为Object类型。空对象类型字面量“{}”强调的是不包含属性的对象类型,同时也可以作为Object类型的代理来使用。最后,也要注意在某些场景中新的object类型可能是更加合适的选择。

4. 弱类型

弱类型(Weak Type)指的是同时满足以下条件的对象类型:

let config: {
    url?: string;
    async?: boolean;
    timeout?: number;
};
5. 多余属性

对象多余属性可简单理解为多出来的属性。eg.

const point: { x: number; y: number } = {
    x: 0,
    y: 0,
    z: 0,
 //  ~~~~
 //  z是多余属性
};
上一篇下一篇

猜你喜欢

热点阅读