web-dev-qa-db-ja.com

ES6Mapから拡張するクラスを作成します

ES6マップのカスタム取得/設定機能を回避しようとしています。現在、 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

18
Nate-Wilkins

Babelは、組み込みクラスの拡張をサポートしていないことを明示的に述べています。 http://babeljs.io/docs/usage/caveats/#classes を参照してください。ただし、MapはそもそもES5の機能ではないため、理由は「ES5の制限」ほど単純ではありません。 Mapの実装は、次のような基本的なパターンをサポートしていないようです。

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

これは基本的に、この場合にBabelが生成するものです。問題は、V8を含むMapの実装が過度に制限されており、Map.set.call呼び出しのthisがプロトタイプチェーンにMapを持っているのではなく、正確にMapであることを確認することです。

同じことがPromiseにも当てはまります。

10
user663031

古き良き方法を使用する必要があります。

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ではないMapバインディングを無視することに注意してください。

Promiseを拡張する方法 も参照してください。

2
Moshe Simantov

うん、 プロキシ が完全に到着するまで、あなたがやろうとしていたことを達成する唯一の方法は、マップ/セットなどの組み込みメソッドを自分でシャドウイングすることです。

たとえば、次のようなマップがある場合:

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

これにより、マップ上で独自のカスタムセット/取得を実行する機能が追加されます。マップのサブクラス化ほど優れていないし、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)
}
0
ragamufin