为什么 Map 在 chrome/node 中不可子类化?

Why isn't Map subclassable in chrome/node?

所以 ES 6 给我们带来了 Maps(none 太快了)。为了我自己的邪恶目的,我希望将 Map 子类化,我尝试了以下方法(为清楚起见缩写):

function Foo() {
    return Map.apply(this, [].slice.call(arguments));
}

var bar = new Foo();

在 V8 环境中,这会引发错误 "Map constructor not called with 'new'"。为什么? SpiderMonkey 得到这个 'right':

Map.call({}, [['foo', 'bar']]).get('foo');

将如预期的那样产生 'bar'。在 SpiderMonkey 和 V8 中尝试类似

function Foo() {};
Foo.prototype = new Map();
var bar = new Foo();
bar.set('foo', 'bar');

将失败:'method set called on incompatible object'。所以就有了继承模式。据我从 spec 可以看出(我没有太多的 spec-foo),实际的 Map 对象具有不可访问的内部属性,这些属性是它们正常​​工作所必需的。但为什么 V8 会在第一个模式上抛出错误?这似乎是一个奇怪的决定,尤其是当它在 FF 中按预期工作时。

更新:我注意到 FF 和 chrome 都实现了 Object.setPrototypeOf。以防万一有人偶然发现这个并想到那个,我可以告诉你它失败了。以下两项因不同原因而失败:

//this totally fails, even for custom constructors/objects
var Foo = function(){};
Object.setPrototypeOf(Foo, Map.prototype);
var bar = new Foo(); //bar doesn't have any of the Map properties/methods

//this one has the methods but still throws the 'incompatible object'
//err. Also fails with new Map() instead of Map.prototype
var bar = Object.setPrototypeOf({}, Map.prototype); 

TL;DR

基本上有四种扩展地图的方法(部分来自下面answers/comments):

  1. 向 Map.prototype 添加方法(真丢人)。
  2. Factory/constructor 使用您委托给的内部 Map 实例创建对象
  3. 将属性复制到地图上的 Mixin fn:
function Foo(){this.bar = 'boo!'}
var baz = new Map();
Foo.call(baz);
baz.bar; //yields 'boo!'
baz.set('5', 5).get('5'); //yields 5

或者等待 ES 6 类 登陆您关心的平台

实际上,它在 FF 中并不是真的 'work',因为 FF 也允许通过调用 Map().

来简单地创建地图

然而,根据 http://kangax.github.io/compat-table/es6/,我们在现代浏览器中不兼容子类化(足够吸引人,IE 在这里有一些支持)。

tl;dr V8/SpiderMonkey 还不完全兼容 ES6。

In V8 environments this throws an Error "Map constructor not called with 'new'". Why?

因为新的 ES6 类(包括内置的)应该只能用 new 构造。

SpiderMonkey gets this 'right'

不完全是。 The spec 明确表示

Map is not intended to be called as a function and will throw an exception when called in that manner.

Wishing to subclass Map

是的,这是合适的:

The Map constructor is designed to be subclassable. It may be used as the value in an extends clause of a class definition. Subclass constructors that intend to inherit the specified Map behaviour must include a super call to the Map constructor to create and initialize the subclass instance with the internal state necessary to support the Map.prototype built-in methods.

因此您需要使用

class Foo extends Map {
    // default constructor
}
var bar = new Foo();
bar.set('foo', 'bar');

您应该可以这样做:

function Foo() {
    return new (Map.bind(this, [].slice.call(arguments)));
}

var bar = new Foo();

致 2018 年 运行 参与其中的任何人:

import ES6Map from 'es6-map/polyfill'

class MyMap extends ES6Map {
  constructor () {
    super()
  }
}