Factory / class that extends its own object

I am trying to extend a built-in Map object. This is the part that works:

var Attributes = class extends Map {

    get text () {
        let out = [];
        for ( let [ k, v ] of this.entries() ) {
            out.push( k + '="' + v + '"' );
        }
        return out.join( ' ' );
    }

    // simplified for brevity!
    set text ( raw ) {
        this.clear();
        var m, r = /(\w+)="([^"]+)"/g;
        while ( ( m = r.exec( raw ) ) ) {
            this.set( m[1], m[2] );
        }
    }
};

var a = new Attributes();
a.text = 'id="first"';
console.log( a.get( 'id' ) ); // first
a.set( 'id', 'second' );
console.log( a.text ); // id="second"
Run codeHide result

But I want to easily integrate this class into my library and rather expose the factory method, which performs the dual task as a constructor. Users do not need to know that this particular method is unusual. This will just make things worse. However, my own code should be able to use instanceof for input validation purposes. For this, I am going to:

var obj = {};
obj.attr = function ( text ) {
    if ( new.target ) {
        this.text = text;
    } else {
        return new obj.attr( text );
    }
};

console.log( obj.attr() instanceof obj.attr ); // true
Run codeHide result

The above also works. However, no matter how I try to combine both approaches, Chrome and Firefox generate different errors. The code below, for example. throws a TypeError "this.entries (...) [Symbol.iterator] is not a function":

var obj = {};
obj.attr = function ( text ) {
    if ( new.target ) {
        this.text = text;
    } else {
        return new obj.attr( text );
    }
};

obj.attr.prototype = Object.assign( new Map(), {
    get text () {
        let out = [];
        for ( let [ k, v ] of this.entries() ) {
            out.push( k + '="' + v + '"' );
        }
        return out.join( ' ' );
    },
    // simplified for brevity!
    set text ( raw ) {
        this.clear();
        var m, r = /(\w+)="([^"]+)"/g;
        while ( ( m = r.exec( raw ) ) ) {
            this.set( m[1], m[2] );
        }
    }
} );
Run codeHide result

What am I missing and / or misunderstanding here?

. Object.setPrototypeOf(), , , . Reflect.construct(). , Object.setPrototypeOf(). , , , , -, .

var Attributes = class extends Map {
    
    constructor ( text ) {
        super();
        this.text = text;
    }
    
    get text () {
        let out = [];
        for ( let [ k, v ] of this.entries() ) {
            out.push( k + '="' + v + '"' );
        }
        return out.join( ' ' );
    }
    
    // simplified for brevity!
    set text ( raw ) {
        this.clear();
        if ( !raw ) return;
        var m, r = /(\w+)="([^"]+)"/g;
        while ( ( m = r.exec( raw ) ) ) {
            this.set( m[1], m[2] );
        }
    }
};

var obj = {};

obj.attr = function ( text ) {
    if ( new.target ) {
        return Reflect.construct( Attributes, [ text ], obj.attr );
    } else {
        return new obj.attr( text );
    }
};

obj.attr.prototype = Object.create( Attributes.prototype );

console.log( obj.attr() instanceof obj.attr );
console.log( obj.attr() instanceof Attributes );
console.log( obj.attr() instanceof Map );

var a = obj.attr();
a.text = 'id="first"';
console.log( a.get( 'id' ) );
a.set( 'id', 'second' );
console.log( a.text );
Hide result
+4
1

, Object.assign /, . , getter (get text) , obviosly fail:

MDN:

Object.assign() . [[Get]] [[Set]] , . , . , , Object.getOwnPropertyDescriptor() Object.defineProperty().

, :

obj.attr.prototype = new Map();
var settings = {
  get text(){},
  set text(v){}
};

for(key in settings){
  Object.defineProperty(
    obj.attr.prototype,
    key,
    Object.getOwnPropertyDescriptor(settings,key)
  );
}

. . this.clear() , , , . , :

1) factory:

 {
  let internal = class extends Map {
   constructor(text){
     super();
     this.text = text;
   };
   set text(v){};
   get text(){};
  };

  var obj = {
   attr(text){
     return new internal(text);
    }
  };
}

2) :

 obj.attr = function(text){
   if(this === window) return new obj.attr(text);
   this.map = new Map();
   this.text = text;
};
obj.attr.prototype = {
  set text(v){
   this.map.set("text",v);
  },
  get text(){
   return this.map.get("text");
  }
};
+1
source

Source: https://habr.com/ru/post/1684515/


All Articles