124

我想创建一个函数对象,它也有一些属性。例如,在 JavaScript 中,我会这样做:

var f = function() { }
f.someValue = 3;

现在在 TypeScript 中,我可以将其类型描述为:

var f: { (): any; someValue: number; };

但是,如果不需要演员表,我实际上无法构建它。如:

var f: { (): any; someValue: number; } =
    <{ (): any; someValue: number; }>(
        function() { }
    );
f.someValue = 3;

如果没有演员阵容,你将如何构建它?

4

9 回答 9

118

更新:此答案是早期版本的 TypeScript 中的最佳解决方案,但在较新版本中有更好的选择(请参阅其他答案)。

接受的答案有效,并且在某些情况下可能需要,但缺点是没有为构建对象提供类型安全性。如果您尝试添加未定义的属性,此技术至少会引发类型错误。

interface F { (): any; someValue: number; }

var f = <F>function () { }
f.someValue = 3

// type error
f.notDeclard = 3
于 2013-09-05T15:12:25.670 回答
109

这现在很容易实现(typescript 2.x)Object.assign(target, source)

例子:

在此处输入图像描述

这里的神奇之处在于Object.assign<T, U>(t: T, u: U)输入返回交点 T & U

强制将其解析为已知接口也是直截了当的。例如:

interface Foo {
  (a: number, b: string): string[];
  foo: string;
}

let method: Foo = Object.assign(
  (a: number, b: string) => { return a * a; },
  { foo: 10 }
); 

由于输入不兼容而导致的错误:

错误:foo:number 不可分配给 foo:string
错误:number 不可分配给 string[](返回类型)

警告:如果针对较旧的浏览器,您可能需要填充Object.assign。

于 2017-01-25T13:43:14.070 回答
60

TypeScript 旨在通过声明合并来处理这种情况:

您可能还熟悉创建函数然后通过向函数添加属性来进一步扩展函数的 JavaScript 实践。TypeScript 使用声明合并以类型安全的方式构建这样的定义。

声明合并让我们说某物既是函数又是命名空间(内部模块):

function f() { }
namespace f {
    export var someValue = 3;
}

这保留了打字,让我们同时写f()f.someValue.d.ts为现有 JavaScript 代码编写文件时,请使用declare

declare function f(): void;
declare namespace f {
    export var someValue: number;
}

在 TypeScript 中向函数添加属性通常是一种令人困惑或意想不到的模式,因此请尽量避免它,但在使用或转换较旧的 JS 代码时可能需要这样做。这是将内部模块(命名空间)与外部模块混合的唯一一次。

于 2015-10-28T13:47:05.793 回答
33

因此,如果要求是简单地构建该函数并将其分配给“f”而不进行强制转换,那么这是一个可能的解决方案:

var f: { (): any; someValue: number; };

f = (() => {
    var _f : any = function () { };
    _f.someValue = 3;
    return _f;
})();

本质上,它使用自执行函数文字来“构造”一个​​在分配完成之前将匹配该签名的对象。唯一奇怪的是函数的内部声明必须是“any”类型,否则编译器会提示您分配给对象上尚不存在的属性。

编辑:稍微简化了代码。

于 2012-10-07T16:33:12.423 回答
8

老问题,但是对于从 3.1 开始的 TypeScript 版本,您可以像在普通 JS 中一样简单地进行属性分配,只要您使用函数声明或const变量的关键字:

function f () {}
f.someValue = 3; // fine
const g = function () {};
g.someValue = 3; // also fine
var h = function () {};
h.someValue = 3; // Error: "Property 'someValue' does not exist on type '() => void'"

参考在线示例

于 2018-11-26T21:38:21.703 回答
3

我不能说这很简单,但绝对有可能:

interface Optional {
  <T>(value?: T): OptionalMonad<T>;
  empty(): OptionalMonad<any>;
}

const Optional = (<T>(value?: T) => OptionalCreator(value)) as Optional;
Optional.empty = () => OptionalCreator();

如果你好奇,这是我的 TypeScript/JavaScript 版本的要点Optional

于 2018-02-07T23:57:37.367 回答
2

作为快捷方式,您可以使用 ['property'] 访问器动态分配对象值:

var f = function() { }
f['someValue'] = 3;

这绕过了类型检查。但是,它非常安全,因为您必须以相同的方式有意访问该属性:

var val = f.someValue; // This won't work
var val = f['someValue']; // Yeah, I meant to do that

但是,如果您真的想要检查属性值的类型,这将不起作用。

于 2012-10-11T04:38:37.403 回答
2

更新的答案:由于通过添加交集类型&,因此可以动态“合并”两种推断类型。

这是一个通用助手,它读取某个对象的属性from并将它们复制到一个对象onto上。它返回相同的对象onto,但具有包含两组属性的新类型,因此正确描述了运行时行为:

function merge<T1, T2>(onto: T1, from: T2): T1 & T2 {
    Object.keys(from).forEach(key => onto[key] = from[key]);
    return onto as T1 & T2;
}

这个低级助手仍然执行类型断言,但它在设计上是类型安全的。有了这个助手,我们就有了一个操作符,我们可以用它来解决完全类型安全的 OP 问题:

interface Foo {
    (message: string): void;
    bar(count: number): void;
}

const foo: Foo = merge(
    (message: string) => console.log(`message is ${message}`), {
        bar(count: number) {
            console.log(`bar was passed ${count}`)
        }
    }
);

单击此处在 TypeScript Playground 中试用。请注意,我们已经限制foo为 type Foo,因此 的结果merge必须是完整的Foo. 因此,如果您重命名bar为,bad则会出现类型错误。

注意这里仍然有一种类型的孔,但是。TypeScript 没有提供将类型参数约束为“不是函数”的方法。因此,您可能会感到困惑,并将您的函数作为第二个参数传递给merge,但这是行不通的。因此,在可以声明之前,我们必须在运行时捕获它:

function merge<T1, T2>(onto: T1, from: T2): T1 & T2 {
    if (typeof from !== "object" || from instanceof Array) {
        throw new Error("merge: 'from' must be an ordinary object");
    }
    Object.keys(from).forEach(key => onto[key] = from[key]);
    return onto as T1 & T2;
}
于 2016-03-10T19:14:36.893 回答
0

这与强类型不同,但你可以这样做

var f: any = function() { }
f.someValue = 3;

如果你想像我发现这个问题时那样绕过压抑的强类型。遗憾的是,TypeScript 在完全有效的 JavaScript 上失败了,所以你必须告诉 TypeScript 退出。

“你的 JavaScript 是完全有效的 TypeScript”计算结果为假。(注:使用 0.95)

于 2014-02-21T15:59:26.817 回答