[英]Factory/class that extends native object
我正在嘗試擴展內置Map對象。 這是起作用的部分:
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"
但是我想將該類無縫集成到我的庫中,而是公開一個工廠方法,該方法有雙重作用,用作構造函數。 用戶不需要知道這種特殊方法是不尋常的。 這只會使事情復雜化。 但是,我自己的代碼需要能夠使用instanceof進行輸入驗證。 這就是我要的:
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
以上也是可行的。 但是,無論我如何嘗試將兩種方法結合起來,Chrome和Firefox都會拋出各種錯誤。 下面的代碼例如拋出TypeError“ this.entries(...)[Symbol.iterator]不是函數”:
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] ); } } } );
我在這里想念和/或誤解了什么?
更新:這將有可能使用Object.setPrototypeOf()
但性能損失可能會非常大。 也可以使用Reflect.construct()
。 盡管這與Object.setPrototypeOf()
到底有什么不同,但對我來說並不明顯。 Reflect似乎通常很慢,因為顯然當前沒有瀏覽器對其進行優化。
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 );
它不起作用,因為Object.assign不會復制getter / setter,而是對其進行調用。 因此,將getter(獲取文本)與window一起調用, 這顯然會失敗:
Object.assign()方法僅將可枚舉和自己的屬性從源對象復制到目標對象。 它在源上使用[[Get]],在目標上使用[[Set]],因此它將調用getter和setter。 如果合並源包含getter,則可能不適合將新屬性合並到原型中。 要將屬性定義(包括其可枚舉性)復制到原型中,應改用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)使用類語法和工廠:
{
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");
}
};
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.