Javascript:对象扩展语法是否有“Symbol.iterator”类似物 - { ...obj }?

问题描述 投票:0回答:2

有一个众所周知的符号:

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 使用的实例透明。

javascript class prototype getter spread-syntax
2个回答
1
投票

不,没有。对象文字扩展语法只是选择所有可枚举的自己的属性,就是这样。

常见的解决方法是定义一个 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));


0
投票

另一种方法是通过

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 
© www.soinside.com 2019 - 2024. All rights reserved.