Factory/class 扩展本机对象
Factory/class that extends native object
我正在尝试扩展内置地图对象。这是有效的部分:
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"
但我想将此 class 无缝集成到我的库中,而是公开一个工厂方法,该方法作为构造函数具有双重用途。用户不需要知道这个特定的方法是不寻常的。这只会使事情复杂化。但是,我自己的代码需要能够使用 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 都会抛出各种错误。下面的代码例如抛出类型错误 "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] );
}
}
} );
我错过了什么and/or这里有误会吗?
更新:使用 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 不复制 getters/setters,而是调用它们。所以 getter (获取文本)被调用 this 是 window ,这显然会失败:
The Object.assign() method only copies enumerable and own properties from a source object to a target object. It uses [[Get]] on the source and [[Set]] on the target, so it will invoke getters and setters. This may make it unsuitable for merging new properties into a prototype if the merge sources contain getters. For copying property definitions, including their enumerability, into prototypes Object.getOwnPropertyDescriptor() and Object.defineProperty() should be used instead.
要在不丢失 setter 的情况下将对象复制到 Map 中,可以这样做:
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) 使用 class 语法和工厂:
{
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");
}
};
我正在尝试扩展内置地图对象。这是有效的部分:
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"
但我想将此 class 无缝集成到我的库中,而是公开一个工厂方法,该方法作为构造函数具有双重用途。用户不需要知道这个特定的方法是不寻常的。这只会使事情复杂化。但是,我自己的代码需要能够使用 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 都会抛出各种错误。下面的代码例如抛出类型错误 "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] );
}
}
} );
我错过了什么and/or这里有误会吗?
更新:使用 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 不复制 getters/setters,而是调用它们。所以 getter (获取文本)被调用 this 是 window ,这显然会失败:
The Object.assign() method only copies enumerable and own properties from a source object to a target object. It uses [[Get]] on the source and [[Set]] on the target, so it will invoke getters and setters. This may make it unsuitable for merging new properties into a prototype if the merge sources contain getters. For copying property definitions, including their enumerability, into prototypes Object.getOwnPropertyDescriptor() and Object.defineProperty() should be used instead.
要在不丢失 setter 的情况下将对象复制到 Map 中,可以这样做:
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) 使用 class 语法和工厂:
{
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");
}
};