【问题标题】:How to clone an object with an arrow function in JavaScript?如何在 JavaScript 中使用箭头函数克隆对象?
【发布时间】:2019-01-27 02:50:49
【问题描述】:

我有这个 JavaScript 代码的 sn-p:

class Foo {
    constructor() {
        this.b = 1;
        this.getB = () => { return this.b; };
    }
}

const normalFoo = new Foo();
const clonedFoo = magicClone(normalFoo);

clonedFoo.b = 5;

console.log(clonedFoo instanceof Foo); // should be true
console.log(clonedFoo.getB()); // should be 5

我想知道我可以用什么替换 magicClone 以获得所需的结果(例如,尊重箭头函数绑定的克隆)。

我可以接受任何类型的可怕黑客攻击,我也可以接受大多数在这种情况下有效的解决方案。这主要是为了我的熏陶:)


请不要将这个问题作为重复问题关闭 - 克隆对象已被多次询问,但我找不到一个可以做到这一点的答案。 Object.assign、lodash的cloneDeep、jQuery的clone等都没有处理这种情况。

【问题讨论】:

  • 我可以想到一个解决方案,它涉及创建类似于 JSON 但不兼容的自定义字符串格式。显然这需要一段时间才能实现,但这看起来像是您会考虑使用的东西,还是更容易弄清楚如何解决这个问题?
  • @PatrickRoberts 我会对任何可行的想法感兴趣,即使它没有完全充实。我在使用这种字符串序列化时遇到的问题是无法将箭头函数重新绑定到适当的上下文,但我很想被证明是错误的!
  • 是的,既然你这么说,我不确定我的解决方案是否有效。我支持 charlietfl,因为这是一种不良做法™
  • @PatrickRoberts 是的,我知道从技术上讲这是一种不好的做法......我的想法是我想构建一个克隆功能,以适应其他人所做的不良做法。无论如何,它主要是供我自己使用的,所以我并不介意 hacky 解决方案。

标签: javascript scope clone deep-copy


【解决方案1】:

所以这里的主要挑战是你有一个箭头函数,它被分配为Foo 实例上的属性。由于箭头函数从它们的封闭上下文继承了它们的this一旦创建它们就不能被重新绑定,让getB 引用克隆的b 字段的唯一方法是重新创建该箭头功能。这意味着您必须以某种方式调用Foo 的构造函数,以便使用正确的上下文重新创建箭头函数。

也就是说,这个magicClone 实现可以解决这个例子:

function magicClone(obj) {
    // Manually create new instance of whatever `obj` is by invoking its constructor:
    const newInstance = new obj.__proto__.constructor()

    // Assign to the new instance all the non-function properties of `obj`.
    Object.assign(newInstance, JSON.parse(JSON.stringify(obj)));

    return newInstance;
}

但是,这种方法的主要缺点是如果obj 的构造函数需要任何参数,那么您无法知道它们应该是什么。因此,这种方法依赖于您的示例 Foo 类具有无参数构造函数这一事实。但是,如果您可以保持在该限制范围内,它确实会为您提供正确的输出:

class Foo {
    constructor() {
        this.b = 1;
        this.getB = () => { return this.b; };
    }
}

function magicClone(obj) {
    const newInstance = new obj.__proto__.constructor()
    Object.assign(newInstance, JSON.parse(JSON.stringify(obj)));
    return newInstance;
}

const normalFoo = new Foo();
normalFoo.otherProp = "must stay the same";

const clonedFoo = magicClone(normalFoo);
clonedFoo.b = 5;

console.log(clonedFoo instanceof Foo); // should be true
console.log(clonedFoo.getB()); // should be 5
console.log(clonedFoo.otherProp)

【讨论】:

  • 避免使用__proto__,它已被弃用。使用Object.getPrototypeOf(或者直接引用obj.constructor,这就是继承的好处)
【解决方案2】:

克隆一个函数基本上是不可能的。它可能是一个闭包,我们既无法知道也无法克隆它关闭的内容。 (关闭 this 值的箭头函数只是这种情况的一个特例。

最好的办法是让您的实例实现克隆协议:

class Foo {
    constructor() {
        this.b = 1;
        this.getB = () => { return this.b; };
    }
    clone() {
        return new Foo // usually passing the current instance's state as arguments
    }
}
function magicClone(o) {
    if (Object(o) !== o) return o; // primitive value
    if (typeof o.clone == "function") return o.clone(); // if available use it
    return Object.assign(Object.create(Object.getPrototypeOf(o)), o); // shallow copy
}

const normalFoo = new Foo();
const clonedFoo = magicClone(normalFoo);

【讨论】:

    【解决方案3】:

    您不能“重新绑定”箭头函数。它总是在定义它的上下文中被调用。使用普通函数即可。

    来自ECMAScript 2015 Spec

    ArrowFunction 中对参数、super、this 或 new.target 的任何引用都必须解析为词法封闭环境中的绑定。通常这将是直接封闭函数的函数环境。

    【讨论】:

    • 是的,但这不足以证明我的问题是不可能的。例如,您可以想象一种解决方案,即创建一个具有相同上下文的新箭头函数。
    猜你喜欢
    • 2010-10-10
    • 2011-07-18
    • 2017-05-22
    • 2013-01-03
    • 2019-01-05
    • 2014-08-09
    • 1970-01-01
    相关资源
    最近更新 更多