创建一个从ES6 Map扩展的类

时间:2015-04-03 14:47:19

标签: javascript inheritance ecmascript-6

尝试在ES6 Maps上摆脱自定义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);
    }
}

我不确定我的语法是否错误,或者我错过了某种类型的实现。但是我收到以下错误:

  

方法Map.prototype.forEach调用不兼容的接收器

5 个答案:

答案 0 :(得分:10)

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

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

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

同样适用于Promise。

答案 1 :(得分:1)

你应该使用旧的方式:

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构造函数会忽略任何不是this的{​​{1}}绑定。

另见How to extend a Promise

答案 2 :(得分:1)

或者,您可以在MapWrapper对象中组成Map()类,并公开自己的API。对象组合的概念在5年前使用得并不多,因此,问题和答案与继承联系在一起并纠结在一起。

答案 3 :(得分:0)

是的,直到Proxies全力以赴,实现你想要做的事情的唯一方法是自己遮蔽地图/集等上的内置方法。

例如,如果您的地图如此:

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

你必须有一些包装器来传递它以添加内置方法,例如对于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)
}

答案 4 :(得分:-1)

不幸的是,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这样重要的事情。

这是我推荐的。 我多年来一直在做的事情 创建一个JavaScript类,您可以随身携带任何演出或项目。称之为“Dictionary”,如果您的环境支持Map,并且您需要一张地图,只需打包Map - 以提高性能。如果您需要继承Map,请继承Dictionary 。我实际上有自己的私人回购与各种算法&amp;我随处可见的数据结构,但你也可以找到完成同样事情的公共回购。有点痛苦,但是这样你并不是每个框架都依赖同样的东西100%环境。