美文网首页
TypeScript-对象类型

TypeScript-对象类型

作者: 奋斗_登 | 来源:发表于2023-04-06 00:02 被阅读0次

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

    const hi = new String('hi');
    

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

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

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

    • Object(首字母大写O)
    • object(首字母小写o)
    • 对象类型字面量
    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()”方法。

    • 类型兼容性
      Object类型有一个特点,那就是除了undefined值和null值外,其他任何值都可以赋值给Object类型。示例如下:
    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类型仅能够赋值给以下三种类型:

    • 顶端类型any和unknown。
      由于所有类型都是顶端类型的子类型,所以object类型能够赋值给顶端类型any和unknown。
    • Object类型。
      Object类型描述了所有对象都共享的属性和方法,所以很自然地表示对象类型的object类型能够赋值给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 };
    

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

    • 可计算属性名的类型为string字面量类型或number字面量类型
    const a: 'a' = 'a';
    const b: 0 = 0;
      
    let obj: {
          [a]: boolean;
          [b]: boolean;
          ['c']: boolean;
          [1]: boolean;
      };
    
    • 可计算属性名的类型为“unique symbol”类型。
    const s: unique symbol = Symbol();
    let obj: {
         [s]: boolean;
    };
    
    • 可计算属性名符合“Symbol.xxx”的形式。
    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)指的是同时满足以下条件的对象类型:

    • 对象类型中至少包含一个属性。
    • 对象类型中所有属性都是可选属性。
    • 对象类型中不包含字符串索引签名、数值索引签名、调用签名和构造签名
      eg
    let config: {
        url?: string;
        async?: boolean;
        timeout?: number;
    };
    
    5. 多余属性

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

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

    相关文章

      网友评论

          本文标题:TypeScript-对象类型

          本文链接:https://www.haomeiwen.com/subject/peurddtx.html