如何使用箭头函数在JavaScript中克隆一个对象?

7

我有这样一段JavaScript代码:

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以获得期望的结果(例如,尊重箭头函数绑定的克隆)。
我可以接受任何可怕的hack,也可以接受在大多数情况下有效的解决方案,只要它们在这种情况下有效。 这主要是为了我的教育 :)
请不要将此问题关闭为重复- 克隆对象已经被问过很多次,但我找不到一个单一的答案来解决这个问题。 Object.assign,lodash的cloneDeep,jQuery的clone等都无法处理此情况。

我可以想到一个解决方案,它涉及创建一种类似于JSON但不兼容的自定义字符串格式。显然,这需要一些时间来实现,但您是否考虑过使用它,或者更容易的方法是找出如何解决此问题? - Patrick Roberts
@PatrickRoberts 我对任何可行的想法都很感兴趣,即使它没有完全成型。我一直遇到的问题是,在这种字符串序列化中,没有办法将箭头函数重新绑定到适当的上下文,但我很乐意被证明是错误的! - thedayturns
是的,既然你这么说,我不确定我的解决方案是否可行。我和charlietfl一样认为这是一个不好的做法™。 - Patrick Roberts
@PatrickRoberts 是的,我知道这在技术上是一个不好的做法...但我的想法是要构建一个克隆函数,能够抵御其他人犯的不良实践。无论如何,这主要是为了我自己使用,所以我并不介意采用hacky的解决方案。 - thedayturns
3个回答

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,这就是继承的好处所在)。 - Bergi

1

基本上无法克隆一个函数。它可能是闭包,我们既不知道也无法克隆它所闭合的内容。(箭头函数中闭合它们的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);

0

你不能“重新绑定”箭头函数。它将始终使用定义时的上下文进行调用。只需使用普通函数即可。

来自ECMAScript 2015规范

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


是的,但这还不足以证明我的问题是不可能的。例如,您可以想象一种解决方案,即创建一个具有相同上下文的新箭头函数。 - thedayturns

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接