2022年9月21日

参照型

詳細な言語機能

この記事では、特定の極端なケースをより良く理解するための高度なトピックを取り扱います。

これは重要ではありません。多くの経験豊富な開発者は、これを知らなくても問題なく生活できます。内部でどのように動作するかを知りたい場合は、読み進めてください。

動的に評価されたメソッド呼び出しは、this を失う可能性があります。

例えば

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

user.hi(); // works

// now let's call user.hi or user.bye depending on the name
(user.name == "John" ? user.hi : user.bye)(); // Error!

最後の行には、user.hi または user.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); }
};

// split getting and calling the method in two lines
let hi = user.hi;
hi(); // Error, because this is undefined

ここで、hi = user.hi は関数を変数に入れ、最後の行では完全にスタンドアロンであるため、this はありません。

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

参照型は「仕様型」です。これを明示的に使用することはできませんが、言語内部で使用されます。

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

  • base はオブジェクトです。
  • name はプロパティ名です。
  • strict は、use strict が有効な場合は true です。

プロパティアクセス user.hi の結果は関数ではなく、参照型の値です。厳格モードでの user.hi の場合は次のようになります。

// Reference Type value
(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)()

追伸:落とし穴があります:)

エラー!

試してみる

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

以下のコードでは、obj.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)() があります。呼び出しは、2行に分割されたかのように機能します。

    f = obj.go; // calculate the expression
    f();        // call what we have

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

  4. (3) と同様のものが、括弧 () の左側に式があります。

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

メソッド呼び出し(代入 =|| など)以外の操作は、それを通常の値に変え、this を設定することを許可する情報を持ちません。

チュートリアルマップ

コメント

コメントする前にこちらをお読みください...
  • 改善するための提案がある場合は、コメントする代わりに GitHub issue を提出するか、プルリクエストを送信してください。
  • 記事の内容が理解できない場合は、詳しく説明してください。
  • 数語のコードを挿入するには <code> タグを使用し、数行の場合は <pre> タグで囲み、10行を超える場合はサンドボックスを使用してください(plnkr, jsbin, codepen…)