这个问题直接类似于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甚至提供了一个方法实现。我怎么使用它?


当前回答

自OP以来将近9年,这个问题仍然存在。我真的很想爱上Typescript。通常我都能成功。但它在打字安全方面的漏洞是我捏着的鼻子挡不住的恶臭。

我的解决方案并不完美。但我的观点是,它们比大多数更常用的解决方案要好。鉴别符已被证明是一种糟糕的实践,因为它们限制了可伸缩性并完全违背了类型安全的目的。我的两个最漂亮的解决方案是,按顺序排列:

Class Decorator: Recursively scans the typed object's members and computes a hash based on the symbol names. Associates the hash with the type name in a static KVP property. Include the type name in the hash calculation to mitigate risk of ambiguity with ancestors (happens with empty subclasses). Pros: It's proven to be the most trustworthy. It is also provides very strict enforcements. This is also similar to how other high-level languages natively implement polymorphism. Howbeit, the solution requires much further extension in order to be truly polymorphic. Cons: Anonymous/JSON objects have to be rehashed with every type check, since they have no type definitions to associate and statically cache. Excessive stack overhead results in significant performance bottlenecks in high load scenarios. Can be mitigated with IoC containers, but that can also be undesirable overhead for small apps with no other rationale. Also requires extra diligence to apply the decorator to every object requiring it.

Cloning: Very ugly, but can be beneficial with thoughtful strategies. Create a new instance of the typed object and reflexively copy the top-level member assignments from the anonymous object. Given a predetermined standard for passage, you can simultaneously check and clone-cast to types. Something akin to "tryParse" from other languages. Pros: In certain scenarios, resource overhead can be mitigated by immediately using the converted "test" instance. No additional diligence required for decorators. Large amount of flexibility tolerances. Cons: Memory leaks like a flour sifter. Without a "deep" clone, mutated references can break other components not anticipating the breach of encapsulation. Static caching not applicable, so operations are executed on each and every call--objects with high quantities of top-level members will impact performance. Developers who are new to Typescript will mistake you for a junior due to not understanding why you've written this kind of pattern.

All totalled: I don't buy the "JS doesn't support it" excuse for Typescript's nuances in polymorphism. Transpilers are absolutely appropriate for that purpose. To treat the wounds with salt: it comes from Microsoft. They've solved this same problem many years ago with great success: .Net Framework offered a robust Interop API for adopting backwards compatibility with COM and ActiveX. They didn't try to transpile to the older runtimes. That solution would have been much easier and less messy for a loose and interpreted language like JS...yet they cowered out with the fear of losing ground to other supersets. Using the very shortcomings in JS that was meant to be solved by TS, as a malformed basis for redefining static typed Object-Oriented principle is--well--nonsense. It smacks against the volumes of industry-leading documentation and specifications which have informed high-level software development for decades.

其他回答

现在这是可能的,我刚刚发布了一个增强版的TypeScript编译器,它提供了完整的反射功能。您可以从类的元数据对象实例化类,从类构造函数检索元数据,并在运行时检查接口/类。你可以在这里查看

使用的例子:

在你的一个typescript文件中,创建一个接口和一个实现它的类,如下所示:

interface MyInterface {
    doSomething(what: string): number;
}

class MyClass implements MyInterface {
    counter = 0;

    doSomething(what: string): number {
        console.log('Doing ' + what);
        return this.counter++;
    }
}

现在让我们打印一些已实现接口的列表。

for (let classInterface of MyClass.getClass().implements) {
    console.log('Implemented interface: ' + classInterface.name)
}

使用reflect -ts编译并启动它:

$ node main.js
Implemented interface: MyInterface
Member name: counter - member kind: number
Member name: doSomething - member kind: function

有关接口元类型的详细信息,请参阅reflect .d.ts。

更新: 您可以在这里找到一个完整的工作示例

这里还有另一个选项:模块ts-interface-builder提供了一个构建时工具,可以将TypeScript接口转换为运行时描述符,ts-interface-checker可以检查对象是否满足它。

对于OP的例子,

interface A {
  member: string;
}

首先运行ts-interface-builder,它会生成一个带有描述符(比如foo-ti)的新简洁文件。Ts,你可以这样用:

import fooDesc from './foo-ti.ts';
import {createCheckers} from "ts-interface-checker";
const {A} = createCheckers(fooDesc);

A.check({member: "hello"});           // OK
A.check({member: 17});                // Fails with ".member is not a string" 

你可以创建一个单行类型保护函数:

function isA(value: any): value is A { return A.test(value); }

Typescript 2.0引入了带标签的联合

Typescript 2.0特性

interface Square {
    kind: "square";
    size: number;
}

interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}

interface Circle {
    kind: "circle";
    radius: number;
}

type Shape = Square | Rectangle | Circle;

function area(s: Shape) {
    // In the following switch statement, the type of s is narrowed in each case clause
    // according to the value of the discriminant property, thus allowing the other properties
    // of that variant to be accessed without a type assertion.
    switch (s.kind) {
        case "square": return s.size * s.size;
        case "rectangle": return s.width * s.height;
        case "circle": return Math.PI * s.radius * s.radius;
    }
}

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

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

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

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);
}

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

简单的解决方案,与所选的解决方案有相同的缺点,但这个变体捕捉JS错误,只接受对象作为参数,并有一个有意义的返回值。

interface A{
    member:string;
}

const implementsA = (o: object): boolean => {
    try {
        return 'member' in o;
    } catch (error) {
        return false;
    }
}

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

implementsA(a) && console.log("a implements A");
// implementsA("str"); // causes TS transpiler error