简体   繁体   English

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

[英]How to clone an object with an arrow function in JavaScript?

I have this snippet of JavaScript code: 我有这段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

I'd like to know what I can replace magicClone with in order to get the desired result (eg a clone that respects arrow function binding). 我想知道我可以替换magicClone以获得所需的结果(例如,一个尊重箭头功能绑定的克隆)。

I'm fine with any sorts of terrible hacks and I'm also fine with solutions that work most of the time, as long as they work in this case. 我对任何类型的可怕黑客都很好,只要他们在这种情况下工作,我也可以使用大多数时间都有效的解决方案。 This is mostly for my edification :) 这主要是为了我的启发:)


Please don't close this question as a duplicate - cloning an object has been asked many times, but I couldn't find a single answer that does this. 请不要将此问题作为副本关闭 - 克隆对象已被多次询问,但我找不到单一的答案。 Object.assign , lodash's cloneDeep , jQuery's clone, etc all do not handle this case. Object.assign ,lodash的cloneDeep ,jQuery的克隆等都不处理这种情况。

So the main challenge here is that you have an arrow function that is assigned as a property on your Foo instances. 所以这里的主要挑战是你有一个箭头函数,它被指定为你的Foo实例的属性。 Since arrow functions inherit their this from their enclosing context, and they can't be rebound once created , the only way to have getB reference the clone's b field is to recreate that arrow function. 由于箭头功能继承this从他们的包围下, 他们不能反弹一旦建立 ,只有这样才能有getB引用克隆的b字段是重新创建箭头功能。 That means you have to somehow invoke Foo 's constructor so that the arrow function will be recreated with the right context. 这意味着你必须以某种方式调用Foo的构造函数,以便使用正确的上下文重新创建箭头函数。

That said, this magicClone implementation will do the trick for this example: 也就是说,这个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;
}

However, the main drawback of this approach is that if obj 's constructor requires any arguments, there is no way you can know what they should be. 但是,这种方法的主要缺点是如果obj的构造函数需要任何参数,那么你无法知道它们应该是什么。 So this approach relies on the fact that your example Foo class has an argumentless constructor. 因此,这种方法依赖于您的示例Foo类具有无参数构造函数的事实。 But if you can stay within that limititation, it does get you the right output: 但是如果你能保持在这个限制范围内,它确实能为你提供正确的输出:

 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) 

It's fundamentally impossible to clone a function. 克隆函数根本不可能。 It might be a closure, and we can neither know nor clone what it closes over. 它可能是一个封闭,我们既不知道也不能克隆它所关闭的东西。 (Arrow functions closing over their this value is just a special case of this). (箭头函数关闭它们的this值只是一个特例)。

Your best bet will be having a cloning protocol implemented by your instances: 您最好的选择是实例实施克隆协议:

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);

You cannot "rebind" an arrow function. 你不能“重新绑定”箭头功能。 It will always be called with the context in which it was defined. 它将始终使用定义它的上下文进行调用。 Just use a normal function. 只需使用正常功能。

From the ECMAScript 2015 Spec : 来自ECMAScript 2015规范

Any reference to arguments, super, this, or new.target within an ArrowFunction must resolve to a binding in a lexically enclosing environment. 对ArrowFunction中的arguments,super,this或new.target的任何引用都必须解析为词法封闭环境中的绑定。 Typically this will be the Function Environment of an immediately enclosing function. 通常,这将是直接封闭函数的函数环境。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM