考虑到工厂功能和序列化的对象类型推荐方法

Recommended method for object typing under consideration of factory functions and serialization

我习惯了静态类型的语言,因此可能会发现 Javascript 中实际上不存在的问题。无论如何,这里是我熟悉的用户特定对象类型检查的方法:

只要您使用构造函数就可以工作。因为我更喜欢factory functionsObject.create,所以instanceof是没有问题的。

适用于构造函数和工厂函数,但一旦序列化开始发挥作用(JSON 或结构化克隆算法)就会被破坏。

适用于工厂函数和序列化,但可能会导致细微错误,并带来与外部不兼容的风险 libraries/frameworks。

Promise/A+ 规范的 thenable 对象(then-方法)就是一个很好的例子。我认为那是有害的。

transducer.js(以及越来越多的其他)使用像 @@transducer/step 这样的伪符号来防止命名冲突,从而给它们的对象一个类型的概念。它只是 duck typing 的一种变体,但符合我的要求。

我倾向于使用 toString 方法进行类型检查,因为它已经通过 Object.prototype.toString 用于本机类型检查。通常 toString returns 对象的字符串表示。如果使用参数调用该方法,我通过让 toString return 一个类型标识符来扩展此行为。以下示例经过高度简化:

var proto = {}, o;

proto.toString: function (_) { return _ === undefined ? JSON.stringify(this) : "someType"};
o = Object.create(proto);
o.a = 123, o.b = "abc", o.c = true;

o.toString(); // {"a":123,"b":"abc","c":true}
o.toString(true); // someType

由于 toString 主要用于日志记录,因此应该可行。但是,toString 在序列化过程中丢失了。

还有其他方法吗?

是否有可以推荐的最佳实践?

更新: 我需要专门针对临时多态性又名 function/method 重载以及在与网络工作者一起工作的上下文中进行类型检查。 Raganwald 在他的博客 post 中使用了术语结构和语义类型,我认为这非常适合这个主题。

我可以说这是否是一种 "best" 实践,但如果您可以访问 es6,则可以使用 Symbol 属性 来指定对象类型。

const Type = Symbol('Type');

const ExampleFactory = function(a, b, c) {

    return {
      a,
      b,
      c,
      [Type]: 'ExampleFactory'
    };
};

符号是唯一的非字符串基元(尽管您可以指定字符串描述)。你不能通过迭代不小心抓取 [Type] 属性,你可以用

进行简单的检查
 obj[Type] === 'ExampleFactory;

Javascript 是一种弱 +"123" === 123 // true 和动态类型 var obj = {}, obj = 123; // 123 语言,具有原型系统。

问题是在这种语言中,通过实例化对象的构造函数在运行时区分对象有多大用处。其实构造函数只是Javascript中的普通函数,无论你做什么,你总是比较原型的身份:

function Ctor() {}
var o = new Ctor();
var p = Object.create(o);

o instanceof Ctor === Ctor.prototype.isPrototypeOf(o) === true;
o.isPrototypeOf(p) === true;

那么为什么要使用构造函数呢?原型也只是普通对象,因此工厂函数是所描述的继承和对象模型的逻辑结果。

对象区分应基于与所用语言的性质相对应的技术。正如我所说,Javascript 是动态类型的,因此非常适合鸭子类型:

var o = {empty: function () { return this.keys().length === 0; }}

if (empty in o) {
  "empty interface implemented";
}

由于对象中的键仅基于字符串,因此与鸭子类型相关的名称冲突是不可避免的table。幸运的是,自 ES2015 以来,符号已经在 Javascript 中找到了自己的方式。

符号有一个身份,因此可以共享而不会相互冲突:

Symbol("foo") === Symbol("foo"); // false

虽然它们有同一性,但符号属于Javascript中的原始类型,与字符串一样是immutable,可以用作对象键。带符号的鸭子打字从单纯的散列 table 查找演变为身份比较。因此,符号可以可靠地说明某个对象是否实现了某个接口:

var o = {};
var empty = Symbol("empty");

o[empty] = function () { return Object.keys(this).length === 0; }

只要一个对象具有 empty 属性,我们就可以确定它实现了正确的接口。更好的是,我们可以混合各种其他接口(使用符号作为键),查找它们中的每一个(通过它们的标识)的存在,所有这些都不会发生名称冲突或增加原型链。

这样就可以轻松实现临时多态性(又名函数重载)。

序列化的最后一句话:序列化意味着丢失几乎所有东西:对象标识、原型链、函数、符号等

因此,每当您使用不共享内存的 Web Worker 或消息传递系统时,您必须准备好复杂对象以进行序列化并在之后恢复它们,例如 folktale