这个问题直接类似于TypeScript中的类类型检查

我需要在运行时找出任何类型的变量是否实现了接口。这是我的代码:

interface A{
    member:string;
}

var a:any={member:"foobar"};

if(a instanceof A) alert(a.member);

如果您在typescript游乐场中输入这段代码,最后一行将被标记为错误,“名称A不存在于当前作用域”。但事实并非如此,该名称确实存在于当前作用域中。我甚至可以更改变量声明为var a: a ={成员:"foobar"};没有编辑的抱怨。在浏览网页并找到其他问题后,我将接口更改为类,但我不能使用对象字面量来创建实例。

我想知道A类型是如何消失的,但看看生成的javascript就能解释这个问题:

var a = {
    member: "foobar"
};
if(a instanceof A) {
    alert(a.member);
}

没有将A表示为接口,因此不可能进行运行时类型检查。

我知道javascript作为一种动态语言没有接口的概念。是否有方法对接口进行类型检查?

typescript游乐场的自动完成显示typescript甚至提供了一个方法实现。我怎么使用它?


当前回答

答案很简单。然而,这种解决方案至少在大约3/4的情况下是可能的(尽管并不总是理想的)。所以,换句话说,这可能与阅读这篇文章的人有关。

假设我有一个非常简单的函数,需要知道参数的接口类型:

const simpleFunction = (canBeTwoInterfaces: interfaceA | interface B) => { 
  // if interfaceA, then return canBeTwoInterfaces.A
  // if interfaceB, then return canBeTwoInterfaces.B
}

得到最多赞的答案往往是使用“功能检查”。也就是说,

const simpleFunction = (canBeTwoInterfaces: interfaceA | interface B) => { 
  if (canBeTwoInterfaces.onlyExistsOnInterfaceA) return canBeTwoInterfaces.A
  else return canBeTwoInterfaces.B
}

然而,在我正在使用的代码库中,我需要检查的接口主要包含可选参数。另外,我团队里的其他人可能会在我不知情的情况下突然改名字。如果这听起来像您正在使用的代码库,那么下面的函数要安全得多。

就像我之前说的,这对很多人来说可能是一件非常明显的事情。尽管如此,要知道何时何地应用给定的解决方案并不明显,不管它是否恰好像下面这样非常简单。

这就是我要做的:

const simpleFunction = (
  canBeTwoInterfaces: interfaceA | interface B,
  whichInterfaceIsIt: 'interfaceA' | 'interfaceB'
) => { 
  if (whichInterfaceIsIt === 'interfaceA') return canBeTwoInterface.A
  else return canBeTwoInterfaces.B
}

其他回答

因为在运行时类型是未知的,所以我写了如下代码来比较未知对象,不是与类型进行比较,而是与已知类型的对象进行比较:

创建正确类型的示例对象 指定它的哪些元素是可选的 将你的未知对象与这个样本对象进行深度比较

下面是我用于深度比较的(接口不可知)代码:

function assertTypeT<T>(loaded: any, wanted: T, optional?: Set<string>): T {
  // this is called recursively to compare each element
  function assertType(found: any, wanted: any, keyNames?: string): void {
    if (typeof wanted !== typeof found) {
      throw new Error(`assertType expected ${typeof wanted} but found ${typeof found}`);
    }
    switch (typeof wanted) {
      case "boolean":
      case "number":
      case "string":
        return; // primitive value type -- done checking
      case "object":
        break; // more to check
      case "undefined":
      case "symbol":
      case "function":
      default:
        throw new Error(`assertType does not support ${typeof wanted}`);
    }
    if (Array.isArray(wanted)) {
      if (!Array.isArray(found)) {
        throw new Error(`assertType expected an array but found ${found}`);
      }
      if (wanted.length === 1) {
        // assume we want a homogenous array with all elements the same type
        for (const element of found) {
          assertType(element, wanted[0]);
        }
      } else {
        // assume we want a tuple
        if (found.length !== wanted.length) {
          throw new Error(
            `assertType expected tuple length ${wanted.length} found ${found.length}`);
        }
        for (let i = 0; i < wanted.length; ++i) {
          assertType(found[i], wanted[i]);
        }
      }
      return;
    }
    for (const key in wanted) {
      const expectedKey = keyNames ? keyNames + "." + key : key;
      if (typeof found[key] === 'undefined') {
        if (!optional || !optional.has(expectedKey)) {
          throw new Error(`assertType expected key ${expectedKey}`);
        }
      } else {
        assertType(found[key], wanted[key], expectedKey);
      }
    }
  }

  assertType(loaded, wanted);
  return loaded as T;
}

下面是我如何使用它的一个例子。

在本例中,我期望JSON包含一个元组数组,其中第二个元素是一个名为User的接口实例(它有两个可选元素)。

TypeScript的类型检查将确保我的示例对象是正确的,然后assertTypeT函数检查未知(从JSON加载的)对象是否与示例对象匹配。

export function loadUsers(): Map<number, User> {
  const found = require("./users.json");
  const sample: [number, User] = [
    49942,
    {
      "name": "ChrisW",
      "email": "example@example.com",
      "gravatarHash": "75bfdecf63c3495489123fe9c0b833e1",
      "profile": {
        "location": "Normandy",
        "aboutMe": "I wrote this!\n\nFurther details are to be supplied ..."
      },
      "favourites": []
    }
  ];
  const optional: Set<string> = new Set<string>(["profile.aboutMe", "profile.location"]);
  const loaded: [number, User][] = assertTypeT(found, [sample], optional);
  return new Map<number, User>(loaded);
}

您可以在用户定义的类型保护的实现中调用这样的检查。

基于Fenton的回答,下面是我实现的一个函数,用于验证给定对象是否具有接口所具有的键(全部或部分)。

根据您的用例,您可能还需要检查每个接口属性的类型。下面的代码不会这样做。

function implementsTKeys<T>(obj: any, keys: (keyof T)[]): obj is T {
    if (!obj || !Array.isArray(keys)) {
        return false;
    }

    const implementKeys = keys.reduce((impl, key) => impl && key in obj, true);

    return implementKeys;
}

用法示例:

interface A {
    propOfA: string;
    methodOfA: Function;
}

let objectA: any = { propOfA: '' };

// Check if objectA partially implements A
let implementsA = implementsTKeys<A>(objectA, ['propOfA']);

console.log(implementsA); // true

objectA.methodOfA = () => true;

// Check if objectA fully implements A
implementsA = implementsTKeys<A>(objectA, ['propOfA', 'methodOfA']);

console.log(implementsA); // true

objectA = {};

// Check again if objectA fully implements A
implementsA = implementsTKeys<A>(objectA, ['propOfA', 'methodOfA']);

console.log(implementsA); // false, as objectA now is an empty object

用户定义类型保护呢?https://www.typescriptlang.org/docs/handbook/advanced-types.html

interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function isFish(pet: Fish | Bird): pet is Fish { //magic happens here
    return (<Fish>pet).swim !== undefined;
}

// Both calls to 'swim' and 'fly' are now okay.

if (isFish(pet)) {
    pet.swim();
}
else {
    pet.fly();
}

我在filter-descriptor.interface.d.ts文件中的@progress/kendo-data-query中找到了一个例子

检查程序

declare const isCompositeFilterDescriptor: (source: FilterDescriptor | CompositeFilterDescriptor) => source is CompositeFilterDescriptor;

示例使用

const filters: Array<FilterDescriptor | CompositeFilterDescriptor> = filter.filters;

filters.forEach((element: FilterDescriptor | CompositeFilterDescriptor) => {
    if (isCompositeFilterDescriptor(element)) {
        // element type is CompositeFilterDescriptor
    } else {
        // element type is FilterDescriptor
    }
});

在我看来,这是最好的方法;在接口上附加一个“Fubber”符号。它的编写速度要快得多,对于JavaScript引擎来说,它比类型保护快得多,它支持接口的继承,如果你需要的话,它使类型保护易于编写。

这就是ES6有符号的目的。

接口

// Notice there is no naming conflict, because interfaces are a *type*
export const IAnimal = Symbol("IAnimal"); 
export interface IAnimal {
  [IAnimal]: boolean; // the fubber
}

export const IDog = Symbol("IDog");
export interface IDog extends IAnimal {
  [IDog]: boolean;
}

export const IHound = Symbol("IDog");
export interface IHound extends IDog {
  // The fubber can also be typed as only 'true'; meaning it can't be disabled.
  [IDog]: true;
  [IHound]: boolean;
}

import { IDog, IAnimal } from './interfaces';
class Dog implements IDog {
  // Multiple fubbers to handle inheritance:
  [IAnimal] = true;
  [IDog] = true;
}

class Hound extends Dog implements IHound {
  [IHound] = true;
}

测试

如果你想帮助TypeScript编译器,这段代码可以放在类型保护中。

import { IDog, IAnimal } from './interfaces';

let dog = new Dog();

if (dog instanceof Hound || dog[IHound]) {
  // false
}
if (dog[IAnimal]?) {
  // true
}

let houndDog = new Hound();

if (houndDog[IDog]) {
  // true
}

if (dog[IDog]?) {
  // it definitely is a dog
}