2020年7月12日

参照型

詳細な言語機能

このセクションでは、特殊なケースをより理解するための高度なトピックについて説明します。

あなたが速く読み進めたいのであれば、スキップまたは別の機会に見てください。

複雑なメソッド呼び出しは、 this を失う可能性があります。例えば:

let user = {
  name: "John",
  hi() { alert(this.name); },
  bye() { alert("Bye"); }
};

user.hi(); // John (シンプルな呼び出しは動作します)

// 今、name に応じて user.hi または user.bye を読んでみましょう
(user.name == "John" ? user.hi : user.bye)(); // Error!

最後の行では、user.hiuser.bye を選択する三項演算子があります。このケースでは、結果は user.hi です。

メソッドは丸括弧 () ですぐに呼び出されます。しかし、それは正しく動きません!

呼び出しはエラーになります、なぜなら、呼び出しの内側の "this" の値は undefined になるからです。

これは動きます (オブジェクトドットメソッド):

user.hi();

これはダメです (評価されたメソッド):

(user.name == "John" ? user.hi : user.bye)(); // Error!

なぜでしょう?なぜそのようなことが起こるのか理解したい場合、obj.method() の呼び出しがどのように機能するのかを理解してみましょう。

参照型の説明

よく見ると、 obj.method() 文に2つの操作があります:

  1. まず、ドット '.' がプロパティ obj.method を抽出します。
  2. 次に、丸括弧 () でそれを実行します。

そして、this についての情報は最初の処理から2つ目の処理へどのように渡されるでしょう?

それらの操作を別々の行に書いた場合、this が失われるのは明らかでしょう:

let user = {
  name: "John",
  hi() { alert(this.name); }
}

// メソッドの取得呼び出しを2行に分けます
let hi = user.hi;
hi(); // Error, this は undefined なので

ここで hi = user.hi は関数を変数の中においています。そして最後の行は完全に独立しています。なので、this がありません。

user.hi() 呼び出しを動作させるために、JavaScriptはトリックを使います – ドット '.' は関数ではなく、特別な参照型を返します。

参照型は “仕様上の型” です。私たちは明示的にそれを使うことはできませんが、言語の中で内部的に使われています。

参照型の値は、3つの値の組み合わせ (base, name, strict) です。ここで:

  • base はオブジェクトです。
  • name はプロパティです。
  • strictuse strict が効いている場合は true です。

user.hi へのプロパティアクセスの結果は、関数ではなく参照型です。strict mode での user.hi はこうなります:

// 参照型の値
(user, "hi", true)

参照型に対して丸括弧 () 呼び出しがされると、それらはオブジェクトとそのメソッドについての完全な情報を受け取り、正しい this (このケースでは user)をセットできます。

参照型はドット . から呼び出し括弧 () へ情報を渡す目的の特別な “中間” の内部型です。

代入 hi = user.hi のような他の操作は、参照型を破棄し、user.hi(関数)の値を渡します。従って、それ以降の操作は全て this を “失います”。

なので、結果として、this の値は、関数がドット obj.method()、もしくは角括弧 obj[method]()構文を使って直接呼び出された場合のみ正しく渡されます。このチュートリアルの後半では、func.bind() など、この問題を解決するためのさまざまな方法を学びます。

サマリ

参照型は言語の内部の型です。

obj.method() 内の . のようなプロパティの読み取りでは、正確なプロパティ値ではなくプロパティ値とそれが取得されたオブジェクトの両方を保持する特別な “参照型” の値を返します。

これはその後に続くメソッド呼び出し () がオブジェクトを取得しそこに this を設定するためです。

その他すべての操作では、参照型は自動的にプロパティ値になります(上のケースでは関数)。

このメカニズム全体は我々の目からは見えません。式を使用して、メソッドがオブジェクトから動的に取得される場合など、微妙なケースでのみ問題になります。

タスク

重要性: 2

このコードの結果はなんでしょう?

let user = {
  name: "John",
  go: function() { alert(this.name) }
}

(user.go)()

P.S. 落とし穴があります :)

エラーです!

やってみましょう:

let user = {
  name: "John",
  go: function() { alert(this.name) }
}

(user.go)() // error!

ほとんどのブラウザでのエラーメッセージは何を間違えているのか理解できません。

user = {...} の後にセミコロンがないため、エラーになります。

JavaScript 括弧 (user.go)() の前にはセミコロンを想定していないので、このようにコードを解釈します:

let user = { go:... }(user.go)()

そして、このようなジョイント式は構文的にはオブジェクト { go: ...} を引数 (user.go) をもつ関数として呼びだすことができます。また、それは let user と同じ行で起こります。なので、user オブジェクトはまだ定義されていないのでエラーになります。

セミコロンを挿入すると、すべてうまく行きます。:

let user = {
  name: "John",
  go: function() { alert(this.name) }
};

(user.go)() // John

(user.go) の周りの括弧はここではなにもしないことに注意してください。通常それらは操作の順番のために設定されますが、ここではドット . がとにかく最初に動作するので影響がありません。セミコロンだけが関係します。

重要性: 3

下のコードで、user.go() メソッドを4回連続で呼び出すつもりです。

しかし、呼び出し (1)(2)(3)(4) とは異なっています。なぜでしょう?

let obj, method;

obj = {
  go: function() { alert(this); }
};

obj.go();               // (1) [object Object]

(obj.go)();             // (2) [object Object]

(method = obj.go)();    // (3) undefined

(obj.go || obj.stop)(); // (4) undefined

説明します。

  1. これは通常のオブジェクトメソッド呼び出しです。

  2. 同じです。ここでは括弧は操作の順番を変更しません。ドットが最初です。

  3. ここにより複雑な呼び出し (expression).method() があります。この呼出しはまるで2行に分割されたかのようにして動作します。:

    f = obj.go; // 式を計算します。
    f();        // 持っているものを実行します

    ここで、f()this なしの関数として実行されます。

  4. (3) と似たようなもので、ドット . の左側に式を持っています。

(3)(4) の振る舞いを説明するために、プロパティ・アクセサ(ドットまたは角括弧)が参照型の値を返すことを思い出す必要があります。

メソッド呼び出し(代入 =||のような)以外の操作は、 this に設定できる情報を持たない通常の値にします。

チュートリアルマップ

コメント

コメントをする前に読んでください…
  • 自由に記事への追加や質問を投稿をしたり、それらに回答してください。
  • 数語のコードを挿入するには、<code> タグを使ってください。複数行の場合は <pre> を、10行を超える場合にはサンドボックスを使ってください(plnkr, JSBin, codepen…)。
  • 記事の中で理解できないことがあれば、詳しく説明してください。