2017-08-26 127 views
4

我试图扩展内置的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);

+1

对不起,所以你要能够使用'新的属性(...)'和'属性(...)'? –

+0

是的。好吧,那种。在文档中,我会告诉用户使用'obj.attr()'。我打算省略'new obj.attr()'也可以。 –

+1

你写道this.entries会抛出一个错误,但是你不包含this.entries方法的代码。 – Sebastian

回答

1

它不工作,因为Object.assign犯规副本getter/setter方法,而是调用它们。因此,吸气(获取文本)被称为与这个窗口,这将obviosly失败:

MDN:

的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"); 
    } 
}; 
+0

这很有道理。尽管如此,我似乎仍然无法实现。请检查我的编辑! –

+0

@peter coester也编辑过;) –

+0

当然。地图不是POJO,而是'obj.attr.prototype = new Map();'应该将'obj.attr'对象变成常规的地图对象,不是吗?当我将'console.log(this)'添加到setter和getter时,我会看到我期望看到的扩展Map对象。 –