有一个众所周知的符号:
Symbol.iterator
,当定义为对象上的生成器函数属性时,允许在 [...object]
语法中使用该对象。所以,你可以这样做:
const newArray = [ ...foo, ...object, ...bar ];
但是,我找不到该功能的类似物,允许这样做:
const newObject = { ...foo, ...object, ...etc };
或
Object.assign
具有非自有属性。
举个例子:带有 get prop() / set prop()
访问器的 ES6 类的实例 - 它们是在类构造函数的 .prototype
属性上定义的:
const C = class {
#num = 42;
#str = 'aaa';
get num() { return this.#num; }
set num(val) { /* validate new value */ this.#num = val; return true; }
get str() { return this.#num; }
set str(val) { /* validate new value */ this.#num = val; return true; }
}
const obj = new C();
现在
obj
具有针对 obj.num = ...
和 obj.str = ...
的内置验证。但是,它不能在 {...foo, ...obj}
或 Object.assign(foo, obj)
中使用,因为访问器位于原型上。代理可以传播,属性访问可以被捕获以进行验证,但是我的目标之一是使访问obj.prop
的性能尽可能接近访问普通对象prop。看看这个:
const p = new Proxy({ num: 0 }, {});
suite.add('instance with class-accessors', () => obj.num++);
suite.add('proxy', () => p.num++);
suite.run();
instance with class-accessors x ***235***,971,841 ops/sec ±0.20% (86 runs sampled)
proxy x ***1***,014,238 ops/sec ±1.91% (84 runs sampled)
速度慢了两个数量级! 那么,有没有办法允许
{ ...obj }
/ Object.assign
用于具有基于原型的 getter 的实例?
我做了实验,以防万一,在课堂上定义
*[Symbol.iterator]() {...}
,产生Object.entries
风格的[prop, val]
对,但没有成功。在 MDN 的“众所周知的符号”上没有找到任何有用的东西。期望找到诸如 Symbol.entries
之类的东西,可以控制扩展/分配并使具有 getters 使用的实例透明。
不,没有。对象文字扩展语法只是选择所有可枚举的自己的属性,就是这样。
常见的解决方法是定义一个 getter 方法来序列化一个可以根据需要传播的对象,通常也用于 JSON 转换:
class C {
#num = 42;
#str = 'aaa';
get num() { return this.#num; }
get str() { return this.#num; }
// …
toJSON() {
return {num: this.#num, str: this.#str};
}
}
const object = new C();
const newObject = { x: 1, ...object.toJSON(), y: 2 };
console.log(newObject);
console.log(JSON.stringify(object));
另一种方法是通过
Object.defineProperty()
定义从类原型到 obj 的 getter|setter。然后扩展语法就可以工作了。示例实现:
function makeProtoGSetterEnumerable(target=this,classF=this?.constructor??target.constructor, bindGet=true){
let descriptors= Object.getOwnPropertyDescriptors(classF.prototype)
const log=false;
if(log)console.log("before (Only proto holds the g|setter)\ntarget Desc:",Object.getOwnPropertyDescriptors(target),"\nproto Desc",descriptors);
//You don't want to modify certain props on class e.g. .constructor
let not=['constructor'];
const desc_entries=Object.entries(descriptors).filter( ([prop_key])=> false===not.includes(prop_key) )
for( const [prop_key,desc] of desc_entries ){
//setting unconfigurable prop raises an Error
if(desc.configurable===false || desc.enumerable===true ){continue }
const new_desc={
...desc,
get: desc?.get,
set: desc?.set,
enumerable:true,
configurable:true,
}
if(bindGet){
//Only for better preview in web console, no need to click (...) , but otherwise uneccessary & slightly more inefficent
new_desc.get= new_desc.get.bind(target);
}
Object.defineProperty( target , prop_key ,new_desc )
}
if(!log){return}
const desc_after_proto=Object.getOwnPropertyDescriptors(target.constructor.prototype);
const desc_after=Object.getOwnPropertyDescriptors(target);
console.log("after (Both proto hold the g|setter)\ntarget Desc:",desc_after,"\nproto Desc",desc_after_proto)
}
这可以在构造函数中使用,也可以在特定的对象上使用。给你的课:
const C = class {
#num = 42;
#str = 'aaa';
// constructor(){ makeProtoGSetterEnumerable(this) }
get num() { return this.#num; }
set num(val) { /* validate new value */ this.#num = val;}
get str() { return this.#str; }
set str(val) { /* validate new value */ this.#str = val;}
}
const obj = new C();
console.log( obj , {...obj} )
// {#num:42,#str:'aaa'} {} //Spread is empty
makeProtoGSetterEnumerable(obj);
console.log(obj, ({...obj}))
// {num: 42, str: 'aaa', #num: 42, #str: 'aaa'} {num: 42, str: 'aaa'}
//Spread works now and has only non private getters
//Info chrome allows viewing+editing of .#props in webconsole, but in modules they work as intendet