创建一个 class 扩展自 ES6 Map

Create a class extending from ES6 Map

尝试在 ES6 地图上使用自定义 get/set 功能。目前正在使用 Babel 将我的代码转换为 ES5。

Chrome Version 41.0.2272.101 m

class MyMap extends Map {
    get(key) {
        if (!this.has(key)) { throw new Error(...); }
        return super.get(key);
    }

    set(key) {
        if (this.has(key)) { throw new Error(...); }
        return super.set(key);
    }
}

不确定我是语法错误还是缺少某种实现。但是我收到以下错误:

Method Map.prototype.forEach called on incompatible reciever

Babel 明确表示他们不支持扩展内置 类。参见 http://babeljs.io/docs/usage/caveats/#classes。然而,原因并不像 "limitations in ES5" 那么简单,因为 Map 不是 ES5 的特性。似乎 Map 的实现不支持基本模式,例如

Map.prototype.set.call(mymap, 'key', 1);

这基本上就是 Babel 在这种情况下生成的内容。问题在于包括 V8 在内的 Map 实现过于严格,并检查 Map.set.call 调用中的 this 是否恰好是一个 Map,而不是在其原型链中包含 Map。

同样适用于 Promise。

是的,在 Proxies 全面生效之前,实现您想要做的事情的唯一方法是自己隐藏 built-in 方法对 Map/Set 等。

例如,如果您有这样的地图:

var myMap = new Map([ ['key1', 'value1'], ['key2', 'value2']])

您必须有一些包装器才能将其传递给添加 built-in 方法,例如 get/set:

function proxify(obj){
    var $fnMapGet = function(key){
        console.log('%cmap get', 'color:limegreen', 'key:', key)
        if(!Map.prototype.has.call(this, key)){
            throw(new Error('No such key: '+ key))
        } else {
            return Map.prototype.get.call(this, key)
        }
    }
    var $fnMapSet = function(key, value){
        console.log('%cmap set', 'color:tomato', 'key:', key, 'value:', value)
        if(Map.prototype.has.call(this, key)){
            throw(new Error('key is already defined: ' + key))
        } else {
            if(Map.prototype.get.call(this, key) == value){
                console.log('%cmap set', 'color:tomato', '*no change')
                return this
            }
            return Map.prototype.set.call(this, key, value)
        }
    }

    Object.defineProperty(obj, 'get', {
        get(){
            return $fnMapGet
        }
    })
    Object.defineProperty(obj, 'set', {
        get(){
            return $fnMapSet
        }
    })

    return obj
}

那么:

proxify(myMap)

myMap.get('key1') // <= "value1"
myMap.get('key2') // <= "value2"
myMap.get('key3') // <= Uncaught Error: No such key: key3
myMap.set('key3', 'value3') // <= Map {"key1" => "value1", "key2" => "value2", "key3" => "value3"}
myMap.set('key3', 'another value3') // <= Uncaught Error: key is already defined: key3

这将增加在地图上执行您自己的自定义 set/get 的能力,虽然不如子类化 Map 好,也不像 es6 代理那么简单,但至少它可以工作。

下面的完整代码片段 运行:

var myMap = new Map([ ['key1', 'value1'], ['key2', 'value2']])

function proxify(obj){
 var $fnMapGet = function(key){
  console.log('get key:', key)
  if(!Map.prototype.has.call(this, key)){
   throw(new Error('No such key: '+ key))
  } else {
   return Map.prototype.get.call(this, key)
  }
 }
 var $fnMapSet = function(key, value){
  console.log('set key:', key, 'value:', value)
  if(Map.prototype.has.call(this, key)){
   throw(new Error('key is already defined: ' + key))
  } else {
   if(Map.prototype.get.call(this, key) == value){
    console.log('*no change')
    return this
   }
   return Map.prototype.set.call(this, key, value)
  }
 }

 Object.defineProperty(obj, 'get', {
  get(){
   return $fnMapGet
  }
 })
 Object.defineProperty(obj, 'set', {
  get(){
   return $fnMapSet
  }
 })

 return obj
}

proxify(myMap)
myMap.get('key1')
myMap.get('key2')
try {
  myMap.get('key3')
} catch(ex){
  console.warn('error:', ex.message)
}
myMap.set('key3', 'value3')
try {
  myMap.set('key3', 'another value3')
} catch(ex){
  console.warn('error:', ex.message)
}

你应该使用老办法:

function ExtendedMap(iterable = []) {
  if (!(this instanceof ExtendedMap)) {
    throw new TypeError("Constructor ExtendedMap requires 'new'");
  }

  const self = (Object.getPrototypeOf(this) === Map.prototype) 
    ? this 
    : new Map(iterable);
  Object.setPrototypeOf(self, ExtendedMap.prototype);

  // Do your magic with `self`...

  return self;
}

util.inherits(ExtendedMap, Map);
Object.setPrototypeOf(ExtendedMap, Map);

ExtendedMap.prototype.foo = function foo() {
  return this.get('foo');
}

然后照常使用new

const exMap = new ExtendedMap([['foo', 'bar']]);
exMap instanceof ExtendedMap; // true
exMap.foo(); // "bar"

请注意,ExtendedMap 构造函数会忽略任何不是 Mapthis 绑定。

另见 How to extend a Promise

很遗憾,Babel 不支持它。奇怪的是,您可以在控制台中 运行 以下内容:

clear();

var Store = function Store(data) {
    // var _map = new Map(data);
    this.get = function get(key) {
        console.log('#get', key);
        return S.prototype.get.call(S.prototype, key);  // or return _map.get(key);
    };
    this.set = function set(key, value) {
        S.prototype.set.call(S.prototype, key, value);  // or _map.set(key, value);
        return this;
    };
};
Store.prototype = new Map();  // we could just wrap new Map() in our constructor instead

var s = new Store();

s.set('a', 1);
s.get('a');

但是,运行使用 Babel 进行以下操作是没有用的:

class Store extends Map {
    constructor(...args) {
        super(...args);
        return this;
    }
}

您将在尝试调用 (new Store(['a','1'])).get('a') 时抛出错误。这让我感到恐惧,像 Map 这样重要的东西会被 Babel 的人们完全忽视。

这是我的推荐。 几年来我一直在做的是 创建一个 JavaScript Class,你可以随身携带它去参加任何演出或项目。将其命名为“Dictionary”,如果您的环境支持 Map 并且您需要一个地图,只需将 Map 包裹起来——为了性能起见。如果需要继承自Map,则继承自Dictionary。实际上,我有自己的私人存储库,其中包含各种算法和数据结构,我走到哪里都会随身携带,但您也可以找到 public 存储库来完成同样的事情。有点痛苦,但这样你就不会 100% 依赖于每个框架和环境的相同事物。

或者,您可以在 MapWrapper 对象中组合 Map() class 并公开您自己的 API。对象组合的概念在 5 年前并没有被广泛使用,因此,问题和答案与继承相关联并纠缠在一起。