我试图扩展内置的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()
有何不同,但对我来说并不明显。反射似乎通常很慢,因为显然没有当前浏览器对其进行优化。
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 );

答案 0 :(得分:1)
它不起作用,因为Object.assign不会复制getter / setter,而是调用它们。因此,使用此 window 调用getter(获取文本),这将显然失败:
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()会崩溃,因为它希望 this 是一个地图而不是常规对象。所以还有两个选择:
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");
}
};