41259


テキストフィールドのテキストが編集されたら（保存が必要であると示すため）文字色を変更するコードを次のように書きました。

[code]

    let textinput = document.getElementById("textinput");
    textinput.oldValue = textinput.value;
    textinput.isChanged = false;
    Object.defineProperty(textinput, "changed", {
      set: function(changed) {
        this.isChanged = changed;
        this.style.color = (changed ? "#c88" : null);
      }
    });
    
    textinput.addEventListener("input", function(anEvent) {
      let textinput = anEvent.target;
      let changed = (textinput.value != textinput.oldValue);
      textinput.changed = changed;
    });
[/code]

[code]

    #textinput {
      color: #000;
    }
[/code]

[code]

    <html>
      <body>
        <input id="textinput" type="text" value="Initial Text" />
      </body>
    </html>
[/code]

これは期待通り動作します。入力中のテキストが初期テキストと異なれば文字色は薄い赤色になります。

ただ、コードの記述は次のように改善したいです。

* * *

### 追加のプロパティは役割をネーミングしたプロパティの子にしたい

現在、

[code]

    let textinput = document.getElementById("textinput");
    textinput.oldValue = textinput.value;
    textinput.isChanged = false;
    ...
    
[/code]

とあるように、元のオブジェクトに直接新しいプロパティを追加しています。これらは編集状態のために加えたので、次のように`editing`というプロパティの子にまとめたいです。

[code]

    let textinput = document.getElementById("textinput");
    textinput.editing = {
      oldValue: textinput.value,
      isChanged: false
    };
    ...
    
[/code]

ここで、セッタ`changed`の処理に問題が出ます。

[code]

    Object.defineProperty(textinput.editing, "changed", {
      set: function(changed) {
        this.isChanged = changed;
        // this.style.color = (changed ? "#c88" : null);
      }
    });
    
[/code]

セッタ関数の内部で`this`が`textinput.editing`を指すようになったため、`textinput.style`にアクセスできなくなりました。
**このセッタ関数内から`textinput.style`にアクセスする方法はありませんか？**


CC BY-SA 3.0
2018-01-28T06:18:22.320
41255
2018-01-28T08:45:07.470
question
javascript
プロパティのセッタ内で親の属性を参照したい


いくつか思いつくものを挙げます。

# Option.1: 親要素を参照するプロパティを定義する

DOMオブジェクトにある[parentElement](https://developer.mozilla.org/en-
US/docs/Web/API/Node/parentElement)プロパティのように、「親」となるオブジェクトを循環参照するプロパティは特別珍しくありません。これを用いると次のように書けます。

[code]

    textinput.editing = {
       // 親を参照、プロパティ名は適当なものを
       __parent: textinput,
       // params
       oldValue: textinput.value,
       isChanged: false,
    };
    Object.defineProperty(textinput.editing, "changed", {
        set: function(changed) {
            this.isChanged = changed;
            this.__parent.style.color = (changed ? "#c88" : null);
        }
    });
    
[/code]

# Option.2: setter functionのthisを束縛する

ご存知の通りJSにおける`this`は指すものが文脈によって異なりますが、[Function.prototype.bind](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)により`this`を適当なオブジェクトに固定した函数を生成できます。これを用いて`this`を`textinput`に束縛すると、最初のものに近い形で実装できるでしょう。

[code]

    textinput.editing = {
        oldValue: textinput.value,
        isChanged: false
    };
    Object.defineProperty(textinput.editing, "changed", {
        set: function(changed) {
            console.assert(this === textinput); // test
            this.editing.isChanged = changed;
            this.style.color = (changed ? "#c88" : null);
        }.bind(textinput) // <-- setterとして定義されるFunctionのthisは*常に*textinput
    });
    
[/code]

# 【参考】DOMオブジェクトを内包してしまう

この問題は、ターゲットとなるオブジェクトをあちこちから参照するためにコードが散逸してしまうことが根本にあります。私であれば、これをまとめるため、たとえばエディタ用のクラスを用意して

[code]

    const Editor = function (hostElement) {
        this.host = hostElement;
        this.host.addEventListener(...);
    };
    Object.defineProperty(Editor.prototype, 'changed', {
        set: function (value) {
            this.isChanged = value;
            // update view
            this.host.style.color = changed ? this.config.changedColor : null;
        },
    });
    
    // init
    const editor1 = new Editor(textinput);
    
[/code]

このように対象となるDOMオブジェクトを内包する形で実装すると思います（経験則でこうするとだいたい楽になる）。

そのほか、この種のトラブルは言語機能というより設計に関するものと思われますので、著名なデザインパターンや有名なライブラリのオブジェクトモデルの設計が参考になるかもしれません。


CC BY-SA 3.0
2018-01-28T07:13:19.467
41259
2018-01-28T08:45:07.470
2018-01-28T08:45:07.470
41255
answer