这个问题直接类似于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
}
这里还有另一个选项:模块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); }
下面是我使用类和lodash想出的解决方案:(它有效!)
// TypeChecks.ts
import _ from 'lodash';
export class BakedChecker {
private map: Map<string, string>;
public constructor(keys: string[], types: string[]) {
this.map = new Map<string, string>(keys.map((k, i) => {
return [k, types[i]];
}));
if (this.map.has('__optional'))
this.map.delete('__optional');
}
getBakedKeys() : string[] {
return Array.from(this.map.keys());
}
getBakedType(key: string) : string {
return this.map.has(key) ? this.map.get(key) : "notfound";
}
}
export interface ICheckerTemplate {
__optional?: any;
[propName: string]: any;
}
export function bakeChecker(template : ICheckerTemplate) : BakedChecker {
let keys = _.keysIn(template);
if ('__optional' in template) {
keys = keys.concat(_.keysIn(template.__optional).map(k => '?' + k));
}
return new BakedChecker(keys, keys.map(k => {
const path = k.startsWith('?') ? '__optional.' + k.substr(1) : k;
const val = _.get(template, path);
if (typeof val === 'object') return val;
return typeof val;
}));
}
export default function checkType<T>(obj: any, template: BakedChecker) : obj is T {
const o_keys = _.keysIn(obj);
const t_keys = _.difference(template.getBakedKeys(), ['__optional']);
return t_keys.every(tk => {
if (tk.startsWith('?')) {
const ak = tk.substr(1);
if (o_keys.includes(ak)) {
const tt = template.getBakedType(tk);
if (typeof tt === 'string')
return typeof _.get(obj, ak) === tt;
else {
return checkType<any>(_.get(obj, ak), tt);
}
}
return true;
}
else {
if (o_keys.includes(tk)) {
const tt = template.getBakedType(tk);
if (typeof tt === 'string')
return typeof _.get(obj, tk) === tt;
else {
return checkType<any>(_.get(obj, tk), tt);
}
}
return false;
}
});
}
自定义类:
// MyClasses.ts
import checkType, { bakeChecker } from './TypeChecks';
class Foo {
a?: string;
b: boolean;
c: number;
public static _checker = bakeChecker({
__optional: {
a: ""
},
b: false,
c: 0
});
}
class Bar {
my_string?: string;
another_string: string;
foo?: Foo;
public static _checker = bakeChecker({
__optional: {
my_string: "",
foo: Foo._checker
},
another_string: ""
});
}
在运行时检查类型:
if (checkType<Bar>(foreign_object, Bar._checker)) { ... }
在我看来,这是最好的方法;在接口上附加一个“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
}
你可以在没有instanceof关键字的情况下实现你想要的,因为你现在可以编写自定义类型保护:
interface A {
member: string;
}
function instanceOfA(object: any): object is A {
return 'member' in object;
}
var a: any = {member: "foobar"};
if (instanceOfA(a)) {
alert(a.member);
}
会员众多
如果需要检查大量成员以确定对象是否与您的类型匹配,则可以添加标识符。下面是最基本的示例,并要求您管理自己的鉴别器…您需要深入了解模式,以确保避免重复标识符。
interface A {
discriminator: 'I-AM-A';
member: string;
}
function instanceOfA(object: any): object is A {
return object.discriminator === 'I-AM-A';
}
var a: any = {discriminator: 'I-AM-A', member: "foobar"};
if (instanceOfA(a)) {
alert(a.member);
}