简体   繁体   English

如何像数组解构一样覆盖 ES class 的 object 解构

[英]How to override object destructuring for ES class just like array destructuring

TL;DR TL;博士

How do I make {...myCls} return {...myCls.instanceVar} ?如何让{...myCls}返回{...myCls.instanceVar}

Actual Question实际问题

I'm trying to implement a custom version of *[Symbol.iterator]() { yield this.internalObj; }我正在尝试实现*[Symbol.iterator]() { yield this.internalObj; }的自定义版本*[Symbol.iterator]() { yield this.internalObj; } such that object-spreads of my class perform an object-spread operation to myClass.instanceVar .这样我的*[Symbol.iterator]() { yield this.internalObj; }的对象扩展对myClass.instanceVar执行对象扩展操作。 Specifically, I want to make {...(new MyClass('a', 'b'}))} return {...(new MyClass('a', 'b'})).innerVal} .具体来说,我想让{...(new MyClass('a', 'b'}))}返回{...(new MyClass('a', 'b'})).innerVal} However, it seems we cannot override object-spread logic, we can only override array-spread logic .但是,似乎我们无法覆盖对象扩展逻辑,我们只能覆盖数组扩展逻辑

For example, this is a simple class to create an Array wrapper例如,这是一个简单的 class 来创建一个Array包装器

class MyArray {
    innerArray = [];

    getItem(index) {
        return (index < this.innerArray.length) ? this.innerArray[index] : null;
    }

    setItem(index, val) {
        const innerArrayLength = this.innerArray.length;

        return (index < innerArrayLength)
            ?
                this.innerArray[index] = val
            :
                this.innerArray.push(val)
        ;
    }

    removeItem(index) {
        return this.innerArray.splice(index, 1);
    }

    clear() {
        this.innerArray = [];
    }

    get length() {
        return this.innerArray.length;
    }

    *[Symbol.iterator]() {
        return yield* this.innerArray;
    }
}

// Usage:
let myArr = new MyArray()   // undefined
myArr.setItem(0, 'a')   // 1
myArr.setItem(10, 'b')   // 2
console.log([...myArr])   // (2) [ 0 => "a", 1 => "b" ]

However, what I want is a way to do that with object class instance variables instead of array class instance variables.但是,我想要的是一种使用object class 实例变量而不是数组class 实例变量来做到这一点的方法。

For example, this is what happens when I try to implement a StorageMock class例如,当我尝试实现StorageMock class 时会发生这种情况

class StorageMock {
    storage = {};

    setItem(key, val) {
        this.storage[key] = val;
    }

    getItem(key) {
        return (key in this.storage) ? this.storage[key] : null;
    }

    removeItem(key) {
        delete this.storage[key];
    }

    clear() {
        this.storage = {};
    }

    get length() {
        return Object.keys(this.storage).length;
    }

    key(index) {
        return Object.keys(this.storage)[index] || null;
    }

    *[Symbol.iterator]() {
        return yield* Object.entries(this.storage).map(([ k, v ]) => ({ [k]: v }));
    }
}

let myStore = new StorageMock()    // undefined
myStore.setItem('a', 'hello');    // undefined
myStore.setItem('b', 'world');    // undefined
console.log({...myStore});   // { storage: { a: "hello", b: "world" } }  <== PROBLEM

// Doing the same with localStorage prints out:
// { a: "hello", b: "world" }
// instead of
// { storage: { a: "hello", b: "world" } }

In this case, the Storage API works to spread storage entries when spreading (local|session)Storage , but creating a special StorageMock class does not.在这种情况下,存储 API可以在传播(local|session)Storage时传播存储条目,但创建特殊的StorageMock class 不会。

Point being that I can't make {...storageMockInstance} === {...(storageMockInstance.storage)} .重点是我无法制作{...storageMockInstance} === {...(storageMockInstance.storage)} So how does one override the object -spreading syntax of an ES class?那么如何覆盖 ES class 的object扩展语法?

References/attempts参考/尝试

I've tried various combinations of Object.create() , Object.definePropert(y|ies)() , variants of the in operator (all of which have relevant access-ability defined here ), all depending on the for...in syntax defininition from the generic-spreading-syntax proposal .我已经尝试了Object.create()Object.definePropert(y|ies)()的各种组合, in运算符的变体(所有这些都在此处定义了相关的访问能力),所有这些都取决于for...in 通用扩展语法提案的语法定义中。 But all I've found is that only "standard" destructuring can be used according to references 1 , 2 , and 3 .但我发现只有“标准”解构可以根据参考123使用。

But there has to be a way to do this via ESNext classes.但是必须有一种方法可以通过 ESNext 类来做到这一点。 None of my attempts to accomplish the ability to use actual native class features instead of those available through AMD module syntax .我没有尝试使用实际的本机 class 功能而不是通过AMD 模块语法提供的功能。 It doesn't seem reasonable that I couldn't override these fields in a way similar to how other languages do so.我不能以类似于其他语言的方式覆盖这些字段,这似乎是不合理的。 ie If I could only override how the JS for..in loop works in the same way that Python allows overriding it , then I could spread the inner variable through a forIn() method just like toString() or toJSON() .即,如果我只能覆盖 JS for..in循环的工作方式,就像 Python 允许覆盖它一样,那么我可以通过forIn()方法传播内部变量,就像toString()toJSON()一样。

Note笔记

Please do not respond with @babel/polyfill , core-js , or babel-jest for this question.请不要用@babel/polyfillcore-jsbabel-jest回答这个问题。 It's not only meant for (local|session)Storage , but also just a question on a high-level problem.它不仅适用于(local|session)Storage ,而且只是关于高级问题的问题。

Short answer简短的回答

You cannot.你不能。

Unless you cheat.除非你作弊。 But might not be worth it.但可能不值得。

Actual answer实际答案

The term "array destructuring" might be a slightly misleading.术语“数组解构”可能有点误导。 It actually always starts by getting the iterator of the object and draws values from there until all bindings are satisfied.它实际上总是从获取 object 的迭代器开始,然后从那里提取值,直到满足所有绑定。 In fact, it is not only supposed to be used on arrays.事实上,它不仅应该用在 arrays 上。

 const obj = { *[Symbol.iterator]() { yield 1; yield 2; yield 3; yield 4; } }; //1. take iterator //2. take first three values const [a, b, c] = obj; //1. take iterator (do not continue the old one) //2. take the first value const [x] = obj; console.log(a, b, c, x); // 1, 2, 3, 1

Object destructuring, however, does not have a similar mechanism.然而,Object 解构没有类似的机制。 When using {...x} the abstract operation CopyDataProperties is performed.使用{...x}时,将执行抽象操作CopyDataProperties As the name suggests, it will copy properties, rather than invoke some mechanism to get the data to copy.顾名思义,它将复制属性,而不是调用某种机制来获取要复制的数据。

The properties that will be copied would be将被复制的属性将是

  1. Own - not coming from the prototype.自己的 - 不是来自原型。
  2. A data properties as opposed to an accessor properties (a property defined by a getter and/or setter). 与访问器属性(由 getter 和/或 setter 定义的属性)相对的数据属性
  3. Enumerable.可枚举。
  4. Not part of the excludedNames parameter to the abstract operation.不是抽象操作的excludedNames参数的一部分。 Note: this is only relevant when using spread with a rest target, like const {foo, bar, ...rest} = obj;注意:这仅在使用带有 rest 目标的扩展时才相关,例如const {foo, bar, ...rest} = obj;

What could be done is to lie to the runtime about each of these things.可以做的是在这些事情上对运行时撒谎 This can be done using a Proxy and you need to change the following things:这可以使用代理来完成,您需要更改以下内容:

  1. ownKeys trap to switch what keys would be used for the destructuring. ownKeys陷阱切换哪些键将用于解构。
  2. getOwnPropertyDescriptor trap to make sure the properties are enumerable. getOwnPropertyDescriptor陷阱以确保属性是可枚举的。
  3. get trap to give the value for the property. get陷阱以给出属性的值。

This can be done as a function like this, for example and will make an object behave as if you are using one of its property values:例如,这可以作为 function 来完成,这将使 object 的行为就像您使用其属性值之一一样:

 const obj = { a: 1, b: 2, c: 3, myProp: { d: 4, e: 5, f: 6 } }; const x = {...lieAboutSpread("myProp", obj) }; console.log(x); function lieAboutSpread(prop, obj) { //this will be the false target for spreading const newTarget = obj[prop]; const handler = { // return the false target's keys ownKeys() { return Reflect.ownKeys(newTarget); }, // return the false target's property descriptors getOwnPropertyDescriptor(target, property) { return Reflect.getOwnPropertyDescriptor(newTarget, property); }, // return the false target's values get(target, property, receiver) { return Reflect.get(newTarget, property, receiver); } } return new Proxy(obj, handler); }

So, this is possible .所以,这是可能的 I am however, not sure it is of that much benefit to simply doing {...obj.myProp } .但是,我不确定简单地做{...obj.myProp }是否有那么多好处。 Moreover, the above function could be re-written in a way that does not "lie" at all.而且,上面的 function 可以用完全不“撒谎”的方式重写。 But it becomes extremely boring:但它变得非常无聊:

 const obj = { a: 1, b: 2, c: 3, myProp: { d: 4, e: 5, f: 6 } }; const x = {...lieAboutSpread("myProp", obj) }; console.log(x); function lieAboutSpread(prop, obj) { //this will be the target for spreading return obj[prop]; }

In my opinion, this highlights why the artificial way of masking the object is an overkill.在我看来,这突显了为什么人工掩盖 object 的方法是一种矫枉过正的做法。

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

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