如何像数组解构一样覆盖 ES class 的对象解构
How to override object destructuring for ES class just like array destructuring
TL;DR
如何制作{...myCls}
return{...myCls.instanceVar}
?
实际问题
我正在尝试实现 *[Symbol.iterator]() { yield this.internalObj; }
的自定义版本,以便我的 class 的对象传播对 myClass.instanceVar
执行对象传播操作。
具体来说,我想让 {...(new MyClass('a', 'b'}))}
return {...(new MyClass('a', 'b'})).innerVal}
。不过,好像we cannot override object-spread logic, we can only override array-spread logic.
例如,这是创建 Array
包装器
的简单 class
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" ]
但是,我想要的是一种使用 object class 实例变量而不是 array 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" } }
在这种情况下,Storage API 会在传播 (local|session)Storage
时传播存储条目,但创建一个特殊的 StorageMock
class 不会。
重点是我做不到 {...storageMockInstance} === {...(storageMockInstance.storage)}
。那么如何覆盖 ES class 的 object 扩展语法?
References/attempts
我尝试了 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. But all I've found is that only "standard" destructuring can be used according to references 1, 2, and 3 的各种组合。
但是必须有一种方法可以通过 ESNext classes 来做到这一点。 None 我尝试实现使用实际本机 class 功能的能力 而不是 通过 AMD module syntax. It doesn't seem reasonable that I couldn't override these fields in a way similar to how other languages do so. i.e. 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()
.
可用的功能
备注
请不要用 @babel/polyfill
、core-js
或 babel-jest
回答这个问题。它不仅适用于 (local|session)Storage
,而且还只是一个高级问题的问题。
TL;DR
你不能。
除非你作弊。但可能不值得。
实际答案
术语“数组解构”可能有点误导。它实际上 总是 从获取对象的迭代器开始,并从那里提取值,直到满足所有绑定。它不是,实际上只应该用在数组上。
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
然而,对象解构没有类似的机制。当使用 {...x}
时,执行抽象操作 CopyDataProperties
。顾名思义,它将复制属性,而不是调用某种机制来获取要复制的数据。
将被复制的属性是
- 自己的 - 不是来自原型。
- A data property as opposed to an accessor property(由getterand/orsetter定义的属性)。
- 可枚举。
- 不是抽象操作的
excludedNames
参数的一部分。注意:这仅在将 spread 与 rest 目标一起使用时才相关,例如 const {foo, bar, ...rest} = obj;
可以做的是对运行时说谎关于这些事情中的每一个。这可以使用 Proxy 完成,您需要更改以下内容:
ownKeys
trap 切换用于解构的键。
getOwnPropertyDescriptor
trap 以确保属性可枚举。
get
trap 为 属性. 赋值
这可以像这样作为一个函数来完成,例如,这将使一个对象的行为就像您正在使用其 属性 值之一一样:
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);
}
所以,这是可能。然而,我不确定简单地做 { ...obj.myProp }
是否有那么大的好处。此外,上述函数可以以完全不“说谎”的方式重写。但是变得极度无聊:
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];
}
在我看来,这凸显了为什么人为掩盖对象的方式是一种矫枉过正。
TL;DR
如何制作{...myCls}
return{...myCls.instanceVar}
?
实际问题
我正在尝试实现 *[Symbol.iterator]() { yield this.internalObj; }
的自定义版本,以便我的 class 的对象传播对 myClass.instanceVar
执行对象传播操作。
具体来说,我想让 {...(new MyClass('a', 'b'}))}
return {...(new MyClass('a', 'b'})).innerVal}
。不过,好像we cannot override object-spread logic, we can only override array-spread logic.
例如,这是创建 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" ]
但是,我想要的是一种使用 object class 实例变量而不是 array 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" } }
在这种情况下,Storage API 会在传播 (local|session)Storage
时传播存储条目,但创建一个特殊的 StorageMock
class 不会。
重点是我做不到 {...storageMockInstance} === {...(storageMockInstance.storage)}
。那么如何覆盖 ES class 的 object 扩展语法?
References/attempts
我尝试了 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. But all I've found is that only "standard" destructuring can be used according to references 1, 2, and 3 的各种组合。
但是必须有一种方法可以通过 ESNext classes 来做到这一点。 None 我尝试实现使用实际本机 class 功能的能力 而不是 通过 AMD module syntax. It doesn't seem reasonable that I couldn't override these fields in a way similar to how other languages do so. i.e. 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()
.
备注
请不要用 @babel/polyfill
、core-js
或 babel-jest
回答这个问题。它不仅适用于 (local|session)Storage
,而且还只是一个高级问题的问题。
TL;DR
你不能。
除非你作弊。但可能不值得。
实际答案
术语“数组解构”可能有点误导。它实际上 总是 从获取对象的迭代器开始,并从那里提取值,直到满足所有绑定。它不是,实际上只应该用在数组上。
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
然而,对象解构没有类似的机制。当使用 {...x}
时,执行抽象操作 CopyDataProperties
。顾名思义,它将复制属性,而不是调用某种机制来获取要复制的数据。
将被复制的属性是
- 自己的 - 不是来自原型。
- A data property as opposed to an accessor property(由getterand/orsetter定义的属性)。
- 可枚举。
- 不是抽象操作的
excludedNames
参数的一部分。注意:这仅在将 spread 与 rest 目标一起使用时才相关,例如const {foo, bar, ...rest} = obj;
可以做的是对运行时说谎关于这些事情中的每一个。这可以使用 Proxy 完成,您需要更改以下内容:
ownKeys
trap 切换用于解构的键。getOwnPropertyDescriptor
trap 以确保属性可枚举。get
trap 为 属性. 赋值
这可以像这样作为一个函数来完成,例如,这将使一个对象的行为就像您正在使用其 属性 值之一一样:
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);
}
所以,这是可能。然而,我不确定简单地做 { ...obj.myProp }
是否有那么大的好处。此外,上述函数可以以完全不“说谎”的方式重写。但是变得极度无聊:
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];
}
在我看来,这凸显了为什么人为掩盖对象的方式是一种矫枉过正。