【问题标题】:Deep clone in TypeScript (preserving types)TypeScript 中的深度克隆(保留类型)
【发布时间】:2015-12-10 12:06:04
【问题描述】:

我需要在 TypeScript 中深度克隆一个对象。这应该不是问题,因为像 Lodash 这样的库为此提供了适当的功能。但是,这些似乎丢弃了类型信息。

> var a = new SomeClass();
> a instanceof SomeClass;
< true
> var b = _.cloneDeep(a);
> b instanceof SomeClass;
< false

有没有办法在 TypeScript 中克隆对象,同时保留这些输入信息?

【问题讨论】:

  • 类似b: typeof a ?
  • 自动?不,您需要在对象及其嵌套对象上实现克隆方法。
  • 保留 Typescript 类型和保留 SomeClass.prototype 不是一回事。请参阅@Retsam 答案和developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…。 Typescript 类型在运行时被删除。
  • @BrunoGrieder 很好地链接了 MDN 文档的实例;我已将其编辑到我的答案中。
  • 我今天(2019 年 11 月)使用 lodash 4.17.15 运行相同的测试,似乎测试成功,即 instanceof 在两种情况下都返回 true。这期间有什么变化吗?

标签: javascript typescript lodash


【解决方案1】:

Typescript 不会在这里丢弃类型信息。在DefinitelyTyped lodash.d.ts文件中,可以看到cloneDeep被定义为

cloneDeep<T>(
    val: T,
    customizer?: (value: any) => any,
    thisArg?: any
) : T

忽略我们不关心的参数,它将T 作为输入,并吐出T 作为输出。所以 Typescript 不会丢失任何类型信息;它认为cloneDeep 的输出与输入的类型相同。

您应该能够通过您的编辑器验证这一点:假设您有一些编辑器可以让您检查变量的类型或自动完成方法(如果您不这样做,我强烈建议您这样做)。


那么为什么typeof 没有按预期工作?这是因为 Typescript 类型信息不会延续到运行时。 instanceof 是一个原生 JS 操作符,Typescript 不会改变它的行为,你可以通过运行这个 sn-p 看到:

"use strict";
class A {}

let a = new A();
let b = _.cloneDeep(a);

if (b instanceof A) {
  alert("b is an instance of A");
} else {
  alert("b is not an instance of A");
}
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"&gt;&lt;/script&gt;

b instanceof A 为 false 的原因是 instanceof 正在检查构造函数:如果函数 A 是 x 原型链中某处的构造函数,x instanceof A 返回 true(请参阅 instanceof 上的 MDN 文档) .然而,Lodash 在克隆对象时不使用构造函数。它不能。 (它怎么知道要传递什么参数?)它创建了一个普通的 JS 对象,其中包含克隆对象的所有方法,但不复制它的原型链。

Lodash 的clone(以及大多数 lodash 的方法,真的)最适合用于处理原始 JS 对象。如果您将它与构造函数和instanceof 一起使用,检查事情会变得有点模糊。


这里的一个解决方案是避免instanceof 检查,并做一些类似于鸭子打字的事情;不要检查对象的构造函数是否是特定函数,而是检查对象是否具有您期望的属性。

另一个解决方案是,如 cmets 中所建议的,在您的类本身上实现一个克隆方法,该方法不会使用 lodash。

class A() {
    clone() {
        var cloned = new A(); //pass appropriate constructor args
        //make other necessary changes to make the state match
        return cloned;
    }
}

【讨论】:

  • 这个答案,尤其是在浏览器中执行时丢失的 TS 类型部分,需要更多的支持。太多人不理解这种区别。
【解决方案2】:

有一篇关于深度克隆的有趣博文http://blog.soulserv.net/understanding-object-cloning-in-javascript-part-ii/。可以使用作者实现的深度克隆clone()函数:

class SomeClass {
    constructor(public test) {

    }
}

let c = new SomeClass("Hey!");
c.test = "Hey!";

console.log(c instanceof SomeClass); // returns true

let cloneC = clone(c, /* resolve circular references */ true);

console.log(cloneC instanceof SomeClass); // returns true

[clone() source code] [Complete source code]

【讨论】:

    【解决方案3】:

    您可以使用Lodash#cloneDeep 实用程序。使用示例:

    import * as _ from "lodash";
    
    ...
    
    {
        this.cloned = _.cloneDeep(data);
    }
    

    【讨论】:

    【解决方案4】:

    您可以使用 JSON 字符串化和解析方法创建自己的深度克隆:

    export function deepClone<T>(obj: T): T {
      return JSON.parse(JSON.stringify(obj)) as T;
    }
    

    【讨论】:

    • 注意:日期对象将被转换为字符串。 IE。不会保留其日期类型。
    • 这在运行时和浏览器环境中的工作方式也不一致
    猜你喜欢
    • 2012-01-05
    • 2019-02-27
    • 1970-01-01
    • 2011-08-13
    • 1970-01-01
    • 1970-01-01
    • 2017-02-23
    • 1970-01-01
    • 2017-09-01
    相关资源
    最近更新 更多