Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
32
Help us understand the problem. What is going on with this article?
@tkykmw

JavaScript(ES2015〜)のProxyで、プロパティにフックする正しいやり方

More than 3 years have passed since last update.

この記事について

ES2015(ES6)で追加されたProxyオブジェクトを使用すると、プロパティへのアクセスに割り込んで、好きな処理を行うことができます。

ただし、使い方に少しコツがあります。この記事では、ネット上で散見される「良くない例」について、なにがどう良くないのかを説明して、気をつけるべきポイントを紹介します。

:x:良くない例

こういう感じのコードが紹介されている場合がありますが、あまり良くありません。

/*
常に値を2倍にするProxy
*/
function doubleProxy_Bad(target) {
    return new Proxy(target, {
        get(target, name) {
            //もとのプロパティの値を取得
            const orig = target[name];
            //2倍にして返す
            return orig * 2;
        },  
        set(target, name, value) {
            //値を2倍にする
            const modified = value * 2;
            //もとのプロパティに設定
            target[name] = modified;
        }   
    }); 
}

:white_check_mark:正しい例

より正しくは、こうです。

function doubleProxy_Good(target) {
    return new Proxy(target, {
        get(target, name, receiver) {
            //もとのプロパティの値を取得
            const orig = Reflect.get(target, name, receiver);
            //2倍にして返す
            return orig * 2;
        },  
        set(target, name, value, receiver) {
            //値を2倍にする
            const modified = value * 2;
            //もとのプロパティに設定
            Reflect.set(target, name, modified, receiver);
        }   
    }); 
}

違いは次の2点です。

  • receiver引数を正しく処理している(get/setの最後の引数)
  • Reflectオブジェクトを使っている

get/set には receiver という引数がある

receiverの正体は、Proxyオブジェクトそのものです。次のコードで確認できます。

const o = { a: 10 };

//どんなプロパティにアクセスしてもreceiverを返す
const p = new Proxy(o, {
    get(target, name, receiver) {
        return receiver;
    }
});

console.log(p.a === p);

一見すると、この引数が何のために存在するのか、わからないかもしれません。しかしgetter/setterが定義されている場合には、大きな違いが出ます。

次のような例を考えましょう。

const obj = {
    a: 1,
    b: 5,
    get c() {
        return this.a + this.b;
    }
};

const p = doubleProxy(obj);

console.log(p.c); //=> ???

p.cの結果は、12でしょうか?24でしょうか?

言いかえると、get cの中のthisは、もとのオブジェクトでしょうか?それともProxyでしょうか?

最初に紹介した「良くない例」だと、thisは必ずもとのオブジェクトになり、結果は12になります。「正しい例」では、thisはProxyになり、24になります。

console.log(doubleProxy_Bad(obj).c);  //=> 12
console.log(doubleProxy_Good(obj).c); //=> 24

どちらの動作が望ましいかは、ケースバイケースです。しかしProxyでないと困る、というケースはあるでしょう。

たとえばgetter/setterの中でメソッドを呼び出す場合には、thisの値によって、そのメソッド呼び出しにもProxyの効果が及ぶかどうかが決まります。

Reflectオブジェクトを使おう

receiver引数とReflectオブジェクトは、そのためにあります。

Reflect.get(target, name[, receiver]);
Reflect.set(target, name, value[, receiver]);

Reflect.getReflect.setの引数にreceiverを与えると、その値がgetter/setter呼び出しのthisになります。

省略すると、targetがthisになります。つまりもとのオブジェクトを直接参照するのと同じです。

どちらが自分の望む動作か、よく考えて使いわけましょう。

まとめ

ProxyとReflectは、セットで覚えておきましょう。get/setに限らず、両者のAPIは統一されており、組み合わせて使うよう設計されています。

thisが何を指しているか」は、JavaScriptを書く上で、常に意識すべきポイントです。getter/setterという、比較的新しい要素についても、忘れないようにしましょう。

参考URL

ES2015(ES6)

ES2016(ES7)

ES Wiki

32
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
tkykmw
プログラマ兼Linuxサーバ管理者 実務でメインに使用しているのはPHP(CakePHP,WordPressなど) 好んで使う言語はJavaScript,Ruby iOSアプリケーション開発(Objective-C)も経験はあるがブランクが長い その他SQL(MySQL),HTML,CSS等

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
32
Help us understand the problem. What is going on with this article?