1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScript のthisを理解する — ECMAScript の仕様から見る

1
Last updated at Posted at 2026-05-18

JavaScript の this を理解する — ECMAScript の仕様から見る

はじめに

本記事では JavaScript の this の正体を ECMAScript 仕様書に基づいて紐解いていきます。

随所に、ECMAScript の原文とその該当箇所へのリンクを併記しています。
(※原文を参照・引用する箇所についてはすべて引用ブロック > を用いて明示します)
原文に添えている日本語の文章は日本語訳ではなく、説明に用いやすいように意訳した
ものです。

【各章について】

1章で「同じ関数なのに呼び方で this が変わる」謎を提示し、2〜3章で this
保持する場所と、this を解決するために必要な Reference Record の正体を解説
します。4章では別例で関数呼び出しの内部処理 OrdinaryCallBindThis を追い、
5〜7章でその応用としての Arrow Function / callapplybind / new
class を順に見ます。8章で1章の謎を完全解明します。

【本記事で扱わないこと】

  • with 文、eval 内の this(非推奨機能。境界引き)
  • モジュールトップレベルの thisundefined になる理由は触れるが詳細は別記事)
  • HTML 仕様で定義される globalThis の中身(言語仕様外)

目次


1. this とは — 同じ関数で答えが変わる謎

this は JavaScript で最もよく語られるテーマですが、「呼ばれ方で変わる」「レシーバ
になる」「Arrow Function だけ違う」といった説明の断片を組み合わせて理解している
方が多いのではないでしょうか。実際、入門書を読んだ直後でも、次のコードがなぜ
こうなるのかをスラスラ説明するのは案外むずかしいです。

"use strict";

const obj = {
  name: "obj",
  who() { return this?.name ?? "(no this)"; },
};

const f1 = () => obj.who();
const f2 = () => { const g = obj.who; return g(); };
const f3 = () => obj.who.call({ name: "X" });

f1(); // "obj"
f2(); // "(no this)"   ← this は undefined
f3(); // "X"

obj.who という 同じ関数オブジェクト を呼んでいるにもかかわらず、this の値は
3 通りに分かれます。さらに、Arrow Function を絡めるとこうなります。

"use strict";

const outer = {
  name: "outer",
  classic: function () { return this?.name ?? "(no this)"; },
  arrow: () => this?.name ?? "(no this)",
};

outer.classic();           // "outer"
outer.arrow();             // "(no this)"  ← ドットで呼んでも "outer" にならない

ドット呼び出しなのに outer.arrow()"outer" を返しません。「ドット呼び出しで
レシーバが this になる」というよく聞くルールでは、この結果は説明できません。

これらの挙動を「そういうものだ」と覚えるのではなく、ECMAScript が this
どこに保存し、いつ・どんな値を書き込んでいるか
を読めば、すべて1つの仕組みの
帰結として説明できます。

本記事ではこの仕組みを 2〜7章で部品ごとに解説し、8章で再びこの章のコードに戻り、
仕様レベルで完全に解明します

本記事のコード前提
本記事のコードはすべて strict mode を前提とします。sloppy mode の this
ボックス化(プリミティブが Object に包まれる、null/undefined が globalThis に
置き換わる)は本記事では扱いません。仕様上は OrdinaryCallBindThis
[[ThisMode]] = global 分岐に該当しますが、現代の JS では "use strict" または
モジュール内に書く前提で問題ありません。


2. Execution Context と [[ThisValue]]

this の話を始める前に、まず this は物理的にどこに保存されているのか
仕様で確認します。結論を先に言うと、this は Function Environment Record の
[[ThisValue]] という内部スロットに保存されています

Execution Context と Environment Record

ECMAScript はコードの実行を Execution Context(以下 EC)という単位で管理します。
グローバルコードに対して Global EC、関数呼び出しごとに新しい関数 EC が作られ、
EC スタックに積まれます。

EC は複数のコンポーネントを持ちますが、this の話で重要なのは
LexicalEnvironment です。これは Environment Record(以下 ER)を指します。

原文:
An execution context is a specification device that is used to track the runtime
evaluation of code by an ECMAScript implementation. ... Execution contexts have
the additional state components listed in Table: LexicalEnvironment, VariableEnvironment, PrivateEnvironment.
§9.4 Execution Contexts

ER にはいくつか種類があり、関数呼び出しごとに新しく作られるのが Function
Environment Record です。

Function Environment Record の内部スロット

Function ER は、変数バインディング(普通の ER の役割)に加えて、this のための
スロットを持ちます。

原文:
Function Environment Records have the additional state fields listed in Table 16.
[[ThisValue]] — This is the this value used for this invocation of the function.
[[ThisBindingStatus]] — lexical, initialized, or uninitialized. If the value is lexical,
this is an ArrowFunction and does not have a local this value.
[[FunctionObject]] — The function object whose invocation caused this Environment Record to be created.
[[NewTarget]] — If this Environment Record was created by the [[Construct]] internal method, ...
§9.1.1.3 Function Environment Records

function_er_layout.png

ここで覚えてほしいのは2つだけです。

  • [[ThisValue]]: その関数呼び出しの this の値
  • [[ThisBindingStatus]]: lexical / initialized / uninitialized の3状態

lexical は5章(Arrow Function)、uninitialized は7章(new)で重要になります。

this 式の評価

ソースコード中の this という式(ThisExpression)は、評価されると次の処理を
走らせます。

原文:
PrimaryExpression : this

  1. Return ? ResolveThisBinding().
    §13.2.1.1 Runtime Semantics: Evaluation

ResolveThisBinding の中身はシンプルで、this を持っている一番内側の ER を
見つけて、その [[ThisValue]] を返す」
だけです。「this を持っている ER を
見つける」のは GetThisEnvironment が担当します。

原文:

  1. Let env be the running execution context's LexicalEnvironment.
  2. Repeat,
    a. Let exists be env.HasThisBinding().
    b. If exists is true, return env.
    c. Let outer be env.[[OuterEnv]].
    d. Assert: outer is not null.
    e. Set env to outer.
    §9.4.3 GetThisEnvironment

つまり、現在の EC の LexicalEnvironment から始めて、[[OuterEnv]] を順番に
辿り、HasThisBinding()true になる ER を見つけます。Function ER と Global ER
HasThisBinding()true、ブロック ER などは false です。

this_resolution.png

この章のまとめ

つまり、this 式の値は、GetThisEnvironment が走査して見つけた最初の Function ER
(または Global ER)の [[ThisValue]] スロットに格納されている値である
、と言えます。

残る疑問は2つです:

  1. [[ThisValue]] には いつ・どんな値が書き込まれる のか?
  2. obj.who()g() で書き込まれる値が違うのはなぜか?

これらを3章(Reference Record)と4章(OrdinaryCallBindThis)で見ます。


3. Reference Record — obj.f は何を返す式か

obj.who() を1つの操作だと思っていると、this の謎は永遠に解けません。
ECMAScript はこれを 2段階 で評価します。

  1. obj.who を評価する → 結果は「値」ではなく Reference Record
  2. その結果に () を適用して呼び出す → このとき Reference Record の情報から
    this を決める

この章では1段階目の Reference Record と、2段階目で this が決まる箇所を見ます。

Reference Record とは

Reference Record は、obj.who のような 「まだ値を取り出していない、参照の途中
状態」
を表す仕様用の型です。ランタイムには現れず、仕様内部だけで使われます。

原文:
The Reference Record type is used to explain the behaviour of such operators as
delete, typeof, the assignment operators, the super keyword and other language
features. ... A Reference Record is a resolved name or (possibly not-yet-resolved)
property binding; its fields are defined by Table 8.
[[Base]] — The value or Environment Record which holds the binding. ...
[[ReferencedName]] — The name of the binding.
[[Strict]] — true if the Reference Record originated in strict mode code, false otherwise.
[[ThisValue]] — If not empty, the Reference Record represents a property binding
that was expressed using the super keyword; it is called a Super Reference Record ...
§6.2.5 The Reference Record Specification Type

reference_record.png

obj.who を評価すると、[[Base]] = obj / [[ReferencedName]] = "who" を持つ
Reference Record ができます(プロパティアクセスの評価規則 §13.3.2.1)。

ここで重要なのは、Reference Record はまだ「who の関数オブジェクト」そのもの
ではない
という点です。[[Base]](呼び出し元のオブジェクト)情報が一緒に
くっついています。この [[Base]] が、後で this になります。

GetValue で Reference は「値」に潰れる

const g = obj.who のような代入では、右辺の Reference Record はすぐに
GetValue を通って ただの関数オブジェクト に変換されます。[[Base]] = obj
の情報はここで失われます。

原文:

  1. If referenceRecord is not a Reference Record, return referenceRecord.
  2. If IsUnresolvableReference(referenceRecord) is true, throw a ReferenceError exception.
  3. If IsPropertyReference(referenceRecord) is true, then
    a. Let baseObj be ? ToObject(referenceRecord.[[Base]]).
    b. ...
    c. Return ? baseObj.[[Get]](referenceRecord.[[ReferencedName]], GetThisValue(referenceRecord)).
  4. Else,
    a. Let base be referenceRecord.[[Base]].
    b. Assert: base is an Environment Record.
    c. Return ? base.GetBindingValue(referenceRecord.[[ReferencedName]], referenceRecord.[[Strict]]).
    §6.2.5.5 GetValue

GetValue の戻り値は ECMAScript の通常の値(ここでは関数オブジェクト)であり、
Reference Record ではありません。だから g には関数オブジェクトしか入って
いません
obj との結びつきはこの時点で消えています。

関数呼び出しでは Reference を直接受け取る

ところが、obj.who() のような関数呼び出しでは、( の左側の評価結果を
GetValue で潰す前に、Reference Record のまま EvaluateCall に渡します

ここがポイントです。

原文:
CallExpression : CallExpression Arguments

  1. Let ref be ? Evaluation of CallExpression.
  2. Let func be ? GetValue(ref).
  3. Let thisCall be this CallExpression.
  4. Let tailCall be IsInTailPosition(thisCall).
  5. Return ? EvaluateCall(func, ref, Arguments, tailCall).
    §13.3.6.1 Function Calls: Runtime Semantics: Evaluation

funcGetValue した関数オブジェクトですが、ref(Reference Record そのもの)
も同時に EvaluateCall に渡されている
ことに注目してください。

そして EvaluateCallthisValue を決定します。

原文:

  1. If ref is a Reference Record, then
    a. If IsPropertyReference(ref) is true, then
    i. Let thisValue be GetThisValue(ref).
    b. Else,
    i. Let refEnv be ref.[[Base]].
    ii. Assert: refEnv is an Environment Record.
    iii. Let thisValue be refEnv.WithBaseObject().
  2. Else,
    a. Let thisValue be undefined.
  3. Let argList be ? ArgumentListEvaluation of arguments.
  4. If func is not an Object, throw a TypeError exception.
  5. If IsCallable(func) is false, throw a TypeError exception.
  6. If tailPosition is true, perform PrepareForTailCall().
  7. Return ? Call(func, thisValue, argList).
    §13.3.6.2 EvaluateCall

これがすべてです。整理すると:

呼び出し式 ( の左の評価結果 EvaluateCallthisValue
obj.who() Reference Record(プロパティ参照、[[Base]] = obj GetThisValue(ref) = obj
g() Reference Record(識別子参照、[[Base]] は ER) WithBaseObject()undefined(普通の ER は常に undefined を返す)
(0, obj.who)() カンマ演算子で GetValue 済み、Reference ではなく値 step 2 へ → undefined

reference_to_thisarg.png

(0, obj.who)() のトリックは知っている方も多いと思いますが、カンマ演算子が
GetValue を呼んで Reference を値に潰すから、その後の呼び出しで this
undefined になる
、というのが仕様レベルでの説明です。

この章のまとめ

関数呼び出しの this は、呼び出し式の「( の左側」を評価した結果が
Reference Record か単なる値かで決まります
。Reference なら GetThisValue
取り出した [[Base]]、ただの値なら undefined(strict 時)が thisValue
なります。

そしてこの thisValue が、次章で見る [[Call]]OrdinaryCallBindThis
thisArgument 引数として渡されていきます。


4. 関数呼び出しの流れ — OrdinaryCallBindThis を別例で追う

ここまでで「this 式は Function ER の [[ThisValue]] を引く」(2章)と
「呼び出し式は Reference の [[Base]] から thisValue を作って Call に渡す」
(3章)が分かりました。残る穴は、Call に渡された thisValue がどうやって
Function ER の [[ThisValue]] に書き込まれるか
です。

それを担当するのが OrdinaryCallBindThis です。

この章では、リング構造のリーフでよくやるように、1章の例とは別の最小例
使って [[Call]] から本体評価までの流れを追います。1章の例で説明すると
「謎の例だから特殊な処理がある」と誤解されがちなので、まずは普通の
obj.method() で機構を見ます。

"use strict";

const counter = {
  n: 0,
  inc() { this.n++; return this.n; },
};

counter.inc(); // 1

全体の流れ

counter.inc() が走るとき、ECMAScript は次の順で動きます。

  1. counter.inc を評価 → Reference Record([[Base]] = counter
  2. EvaluateCallthisValue = counter を決定(3章で見た)
  3. Call(func, thisValue, args)func.[[Call]](thisValue, args)
  4. [[Call]]PrepareForOrdinaryCallOrdinaryCallBindThisOrdinaryCallEvaluateBody
  5. 本体内の this.n++this は ResolveThisBinding で Function ER の [[ThisValue]] を引く

ステップ4を細かく見ます。

[[Call]] の中身

原文:

  1. Let callerContext be the running execution context.
  2. Let calleeContext be PrepareForOrdinaryCall(func, undefined).
  3. Assert: calleeContext is now the running execution context.
  4. If func.[[IsClassConstructor]] is true, then
    a. Let error be a newly created TypeError object.
    b. ... Throw error.
  5. Perform OrdinaryCallBindThis(func, calleeContext, thisArgument).
  6. Let result be Completion(OrdinaryCallEvaluateBody(func, argumentsList)).
  7. Remove calleeContext from the execution context stack and restore callerContext as the running execution context.
  8. If result is a return completion, return result.[[Value]].
  9. Assert: result is a throw completion.
  10. Return ? result.
    §10.2.1 [[Call]]

[[Call]] は次の3つを順に呼ぶだけです:

  • PrepareForOrdinaryCall: 新しい EC と Function ER を作る
  • OrdinaryCallBindThis: Function ER の [[ThisValue]] を書き込む(この章の主役)
  • OrdinaryCallEvaluateBody: 本体を評価する

PrepareForOrdinaryCall: EC と Function ER を作る

原文:

  1. Let callerContext be the running execution context.
  2. Let calleeContext be a new ECMAScript code execution context.
  3. Set the Function of calleeContext to func.
  4. Let calleeRealm be func.[[Realm]].
  5. Set the Realm of calleeContext to calleeRealm.
  6. Set the ScriptOrModule of calleeContext to func.[[ScriptOrModule]].
  7. Let localEnv be NewFunctionEnvironment(func, newTarget).
  8. Set the LexicalEnvironment of calleeContext to localEnv.
  9. Set the VariableEnvironment of calleeContext to localEnv.
  10. Set the PrivateEnvironment of calleeContext to func.[[PrivateEnvironment]].
  11. If callerContext is not already suspended, suspend callerContext.
  12. Push calleeContext onto the execution context stack; calleeContext is now the running execution context.
  13. Return calleeContext.
    §10.2.1.1 PrepareForOrdinaryCall

ここで NewFunctionEnvironment新しい Function ER を作り、新 EC の
LexicalEnvironment がそれを指します。この時点では Function ER の
[[ThisBindingStatus]]uninitialized
です([[ThisValue]] はまだ未定)。

OrdinaryCallBindThis: [[ThisValue]] を書き込む

ここが this の正体です。

原文:

  1. Let thisMode be func.[[ThisMode]].
  2. If thisMode is lexical, return unused.
  3. Let calleeRealm be func.[[Realm]].
  4. Let localEnv be the LexicalEnvironment of calleeContext.
  5. If thisMode is strict, then
    a. Let thisValue be thisArgument.
  6. Else,
    a. If thisArgument is either undefined or null, then
    i. Let globalEnv be calleeRealm.[[GlobalEnv]].
    ii. Assert: globalEnv is a Global Environment Record.
    iii. Let thisValue be globalEnv.[[GlobalThisValue]].
    b. Else,
    i. Let thisValue be ! ToObject(thisArgument).
    ii. NOTE: ToObject produces wrapper objects using calleeRealm.
  7. Assert: localEnv is a Function Environment Record.
  8. Assert: The next step never returns an abrupt completion because localEnv.[[ThisBindingStatus]] is not initialized.
  9. Perform ! BindThisValue(localEnv, thisValue).
  10. Return unused.
    §10.2.1.2 OrdinaryCallBindThis

[[ThisMode]] は関数オブジェクトの内部スロットで、3つの値を取ります:

  • lexical: Arrow Function → step 2 で即 return(5章で詳説)
  • strict: strict mode の通常関数 → thisArgument をそのまま使う
  • global: sloppy mode の通常関数 → null/undefined を globalThis に置換、プリミティブを Object 化(境界引き)

そして最後の BindThisValue が、Function ER の [[ThisValue]] スロットに値を
書き込みます。

原文:

  1. Assert: envRec.[[ThisBindingStatus]] is not lexical.
  2. If envRec.[[ThisBindingStatus]] is initialized, throw a ReferenceError exception.
  3. Set envRec.[[ThisValue]] to value.
  4. Set envRec.[[ThisBindingStatus]] to initialized.
  5. Return unused.
    §9.1.1.3.1 BindThisValue

これで Function ER の [[ThisValue]] = counter[[ThisBindingStatus]] = initialized
になります。本体内の this.n++this が、この [[ThisValue]] を引きます
(2章の GetThisEnvironment)。

ステップ図シリーズで counter.inc() を追う

ここまでの流れを6枚のスナップショットで追います。

ch4_step0_before_call.png

step 0: 呼び出し直前。EC スタックには Global EC のみ。counter オブジェクトと
inc の関数オブジェクトが存在し、関数オブジェクトの [[ThisMode]]strict

ch4_step1_evaluate_callee.png

step 1: counter.inc を評価。Reference Record が生成され、[[Base]] = counter
[[ReferencedName]] = "inc"GetValueinc の関数オブジェクトを取得しつつ、
Reference Record も保持。

ch4_step2_evaluate_call.png

step 2: EvaluateCall が走り、Reference Record の [[Base]] から
thisValue = counter を決定。Call(func, counter, []) を呼ぶ。

ch4_step3_prepare_call.png

step 3: PrepareForOrdinaryCall が走り、新しい EC が EC スタックに push される。
Function ER も作られ、[[ThisBindingStatus]] = uninitialized

ch4_step4_bind_this.png

step 4: OrdinaryCallBindThis が走る。[[ThisMode]] = strict なので
thisValue = counterBindThisValue(counter) で Function ER の [[ThisValue]]
counter[[ThisBindingStatus]]initialized になる。

ch4_step5_this_lookup.png

step 5: 本体 this.n++ が評価される。this 式は ResolveThisBinding
GetThisEnvironment で running EC の LexicalEnvironment(= 上で作った Function ER)
に到達し、[[ThisValue]] = counter を返す。

この章のまとめ

関数呼び出しは内部的に [[Call]]PrepareForOrdinaryCall
OrdinaryCallBindThisOrdinaryCallEvaluateBody の順で進み、this
OrdinaryCallBindThis の中で BindThisValue を経由して Function ER の
[[ThisValue]] に書き込まれる

そして OrdinaryCallBindThis の step 1 が「[[ThisMode]]lexical なら
即 return」となっていることに注目してください。これが次章の Arrow Function の
正体です。


5. Arrow Function — [[ThisMode]] = lexical

「Arrow Function は自分の this を持たない」「外側の this を引き継ぐ」という
説明はよく見ますが、ECMAScript の仕様では this を持たない」のではなく
this を書き込む処理をスキップする」
と表現されます。

関数オブジェクトの [[ThisMode]] スロット

ECMAScript 関数オブジェクトは内部スロットの一つとして [[ThisMode]] を持ち、
これは関数の 作成時 に決まります。

原文:
[[ThisMode]] — Defines how this references are interpreted within the formal
parameters and code body of the function. lexical means that this refers to the
this value of a lexically enclosing function. strict means that the this value is
used exactly as provided by an invocation of the function. global means that a
this value of undefined or null is interpreted as a reference to the global object,
and any other this value is first passed to ToObject.
§10.2 ECMAScript Function Objects, Table: Internal Slots

[[ThisMode]] どの関数か
lexical Arrow Function
strict strict mode の通常関数・メソッド
global sloppy mode の通常関数(本記事では境界引きで深入りしない)

Arrow Function は InstantiateArrowFunctionExpression[[ThisMode]] = lexical
として生成されます。

原文:
ArrowFunction : ArrowParameters => ConciseBody

  1. If name is not present, set name to the empty String.
  2. Let env be the LexicalEnvironment of the running execution context.
  3. Let privateEnv be the running execution context's PrivateEnvironment.
  4. Let sourceText be the source text matched by ArrowFunction.
  5. Let closure be OrdinaryFunctionCreate(%Function.prototype%, sourceText,
    ArrowParameters, ConciseBody, lexical-this, env, privateEnv).
  6. Perform SetFunctionName(closure, name).
  7. Return closure.
    §15.3.4 Runtime Semantics: InstantiateArrowFunctionExpression

OrdinaryFunctionCreatelexical-this 引数を受け取ると [[ThisMode]]
lexical にセットします。Arrow Function かどうかは、パースの段階で決まる
関数オブジェクト側の性質
であって、呼び方の問題ではありません。

OrdinaryCallBindThis の早期 return

4章で見た OrdinaryCallBindThis の step 1〜2 を再掲します。

原文:

  1. Let thisMode be func.[[ThisMode]].
  2. If thisMode is lexical, return unused.
    §10.2.1.2 OrdinaryCallBindThis

たった1行です。Arrow Function は OrdinaryCallBindThis の step 2 で
即 return し、Function ER の BindThisValue を呼びません。

その結果:

  • Function ER の [[ThisValue]]書き込まれない
  • [[ThisBindingStatus]]uninitialized のまま

thismode_branch.png

this 式の評価で外側に走査が伸びる

本体内に this が出てきたとき、2章で見た GetThisEnvironment が走ります。

原文:

  1. Let env be the running execution context's LexicalEnvironment.
  2. Repeat,
    a. Let exists be env.HasThisBinding().
    b. If exists is true, return env.
    c. Let outer be env.[[OuterEnv]].
    d. Assert: outer is not null.
    e. Set env to outer.
    §9.4.3 GetThisEnvironment

ここで効くのが、Function ER の HasThisBinding の定義です。

原文:

  1. If envRec.[[ThisBindingStatus]] is lexical, return false.
  2. Return true.
    §9.1.1.3.2 Function Environment Records: HasThisBinding

Arrow Function は OrdinaryFunctionCreate で Function ER 作成時に
[[ThisBindingStatus]] = lexical がセットされており、HasThisBinding
false を返します。つまり GetThisEnvironmentその Function ER を
スキップし、[[OuterEnv]] を辿って外側に走査を伸ばします

外側にあるのは Arrow Function を定義した時点の ER(外側の関数の Function ER か、
Global ER)。そこに到達したら、その ER の [[ThisValue]] を読みます。これが
「外側の this を引き継ぐ」の正体です。

arrow_this_lookup.png

1章の Arrow Function 例を再考する

1章で見た次の例を思い出してください。

"use strict";

const outer = {
  name: "outer",
  arrow: () => this?.name ?? "(no this)",
};

outer.arrow(); // "(no this)"

outer.arrow() は3章の Reference Record の理屈で thisValue = outer を作り、
Call(arrow, outer, []) を呼びます。しかし arrow[[ThisMode]] = lexical
ため、OrdinaryCallBindThis は step 2 で即 return し、outer という情報は
完全に捨てられます

this 式の評価では、外側のスコープ(モジュールトップレベルなら this
undefined)を読みに行くため、(no this) が返ります。

つまり、ドットで呼んでも、callthis を渡しても、Arrow Function には
何も届きません

この章のまとめ

Arrow Function は呼び出し時に OrdinaryCallBindThis を早期 return することで
Function ER の [[ThisValue]] を未初期化のままにし、結果として this 式は
GetThisEnvironment が外側の ER まで走査を伸ばす

これが「自分の this を持たない」「外側の this を引き継ぐ」の仕組みです。
this をどうこうしているのではなく、this保存処理そのものを
スキップ
しているのです。


6. call / apply / bindthisArgument の明示と BoundFunctionExoticObject

3章で見た通り、this の元となる thisValue は通常、Reference Record の
[[Base]]
から作られます。Function.prototypecall / apply / bind
の3メソッドは、この経路をバイパスして thisArgument を明示的に渡す ための
仕組みです。

callCall を直接呼ぶラッパ

原文:

  1. Let func be the this value.
  2. If IsCallable(func) is false, throw a TypeError exception.
  3. Perform PrepareForTailCall().
  4. Return ? Call(func, thisArg, args).
    §20.2.3.3 Function.prototype.call

f.call(thisArg, ...) は、内部的にはほぼ Call(f, thisArg, args) です。
3章で EvaluateCall が決めていた thisValue を、ユーザーが直接指定する形に
なっただけです。

applycall の args を配列で渡す版

原文:

  1. Let func be the this value.
  2. If IsCallable(func) is false, throw a TypeError exception.
  3. If argArray is either undefined or null, then
    a. Perform PrepareForTailCall().
    b. Return ? Call(func, thisArg).
  4. Let argList be ? CreateListFromArrayLike(argArray).
  5. Perform PrepareForTailCall().
  6. Return ? Call(func, thisArg, argList).
    §20.2.3.1 Function.prototype.apply

call との違いは引数リストの受け渡し形式だけで、this の渡し方は同じです。

bindBoundFunctionExoticObject を作る

bindその場では呼ばずthis と先頭の引数を固定した新しい関数オブジェクトを
返します。この返り値は普通の関数オブジェクトではなく
BoundFunctionExoticObject という特別な種類です。

原文:

  1. Let target be the this value.
  2. If IsCallable(target) is false, throw a TypeError exception.
  3. Let boundFunc be ? BoundFunctionCreate(target, thisArg, args).
  4. ... (length と name の設定が続く)
  5. Return boundFunc.
    §20.2.3.2 Function.prototype.bind

BoundFunctionExoticObject は通常の関数オブジェクトと違う内部スロットを持ちます。

原文:
[[BoundTargetFunction]] — The wrapped function object.
[[BoundThis]] — The value that is always passed as the this value when calling the wrapped function.
[[BoundArguments]] — A list of values whose elements are used as the first arguments
to any call to the wrapped function.
§10.4.1 Bound Function Exotic Objects

bind_layout.png

そして bound 関数の [[Call]] は、内部のターゲット関数を [[BoundThis]] 付きで
呼ぶだけです。

原文:

  1. Let target be func.[[BoundTargetFunction]].
  2. Let boundThis be func.[[BoundThis]].
  3. Let boundArgs be func.[[BoundArguments]].
  4. Let args be the list-concatenation of boundArgs and argumentsList.
  5. Return ? Call(target, boundThis, args).
    §10.4.1.1 [[Call]] of Bound Function Exotic Objects

ここで重要なのは step 5 の Call(target, boundThis, args) です。呼び出し側から
渡された thisArgument は無視され、[[BoundThis]] が代わりに使われます

"use strict";
const f = function () { return this?.name ?? "(no this)"; };
const bound = f.bind({ name: "bound" });
bound.call({ name: "ignored" }); // "bound" ← call の thisArg は無視

これは bound 関数の [[Call]] が、外から渡された thisArgument使わない ように
書かれているからです。

3つを横並びで見る

call_apply_bind_flow.png

経路 thisArgument の出どころ
obj.f() Reference Record [[Base]] = obj(3章)
f.call(x, ...) 第1引数 x
f.apply(x, [...]) 第1引数 x
bound = f.bind(x); bound() bound.[[BoundThis]] = x

Arrow Function に bind しても無視される理由

bind で返るのは BoundFunctionExoticObject で、その [[Call]] は内部のターゲット
関数の [[Call]][[BoundThis]] 付きで呼ぶだけです。でもターゲットが Arrow
Function なら、その [[Call]] が呼ぶ OrdinaryCallBindThis は step 2 で即 return
し、
[[BoundThis]] は捨てられます。

"use strict";
const arrow = () => this;
const bound = arrow.bind({ name: "X" });
bound(); // undefined(モジュールトップレベル)

これは Arrow Function の [[ThisMode]] = lexicalbind より優先される
(というより、bind 後段の OrdinaryCallBindThis で必ず効く)ためです。

この章のまとめ

call / apply / bind の3つは、Reference Record の [[Base]] を経由する
代わりに、[[Call]]thisArgument 引数を直接指定するための異なる経路に
すぎない
。最終的に OrdinaryCallBindThis を経由して Function ER の
[[ThisValue]] に書き込まれる、というメインの流れは変わりません。


7. newclass[[Construct]] と未初期化 this

通常の関数呼び出しは関数オブジェクトの [[Call]] を呼びますが、new f(...)
[[Call]] ではなく [[Construct]] を呼びますthis の作られ方も微妙に
違い、特に class extends した派生クラスでは「super() 前は this が読めない」
という独特の振る舞いがあります。これも [[ThisBindingStatus]] で説明できます。

new[[Construct]] を呼ぶ

原文:

  1. Let callerContext be the running execution context.
  2. Let kind be func.[[ConstructorKind]].
  3. If kind is base, then
    a. Let thisArgument be ? OrdinaryCreateFromConstructor(newTarget, "%Object.prototype%").
  4. Let calleeContext be PrepareForOrdinaryCall(func, newTarget).
  5. Assert: calleeContext is now the running execution context.
  6. If kind is base, then
    a. Perform OrdinaryCallBindThis(func, calleeContext, thisArgument).
    b. Let initializeResult be Completion(InitializeInstanceElements(thisArgument, func)).
    c. If initializeResult is an abrupt completion, then ...
  7. Let constructorEnv be the LexicalEnvironment of calleeContext.
  8. Let result be Completion(OrdinaryCallEvaluateBody(func, argumentsList)).
  9. Remove calleeContext from the execution context stack and restore callerContext as the running execution context.
  10. If result is a throw completion, then return ? result.
  11. Assert: result is a return completion.
  12. If result.[[Value]] is an Object, return result.[[Value]].
  13. If kind is base, return thisArgument.
  14. If result.[[Value]] is not undefined, throw a TypeError exception.
  15. Let thisBinding be ? constructorEnv.GetThisBinding().
  16. Assert: thisBinding is an Object.
  17. Return thisBinding.
    §10.2.2 [[Construct]]

ここで重要な分岐が2つあります:

  • base classextends なし、または通常関数の new f()): step 3 で 新しい
    オブジェクトを作って thisArgument に渡し
    、step 6 で OrdinaryCallBindThis
    呼ぶ。Function ER の [[ThisValue]]本体評価開始時に既に書き込まれている
  • derived classextends あり): step 3 と step 6 を 両方スキップ。Function ER
    [[ThisBindingStatus]]uninitialized のまま本体評価が始まる

new_flow.png

base class の new

class Counter {
  constructor() {
    this.n = 0; // OK: [[ThisValue]] は既に新しいオブジェクト
  }
}
new Counter();

[[ConstructorKind]] = base なので、[[Construct]] の step 3 で
OrdinaryCreateFromConstructor新しい空オブジェクト を作り、それが
thisArgument になります。step 6 の OrdinaryCallBindThis で Function ER の
[[ThisValue]] にセットされ、本体に入った時点で this は使えます。

derived class の new

class Sub extends Counter {
  constructor() {
    // this.x = 1;  // ← ReferenceError: 'this' is not defined
    super();
    this.x = 1;    // OK: super() で初期化された
  }
}
new Sub();

[[ConstructorKind]] = derived の場合、[[Construct]] の step 3 と step 6 は
スキップされます。よって 本体評価開始時、Function ER の [[ThisBindingStatus]]
uninitialized のまま
です。

ここで本体内の this 式が走ると ResolveThisBinding → Function ER の
GetThisBinding が呼ばれます。

原文:

  1. Assert: envRec.[[ThisBindingStatus]] is not lexical.
  2. If envRec.[[ThisBindingStatus]] is uninitialized, throw a ReferenceError exception.
  3. Return envRec.[[ThisValue]].
    §9.1.1.3.3 Function Environment Records: GetThisBinding

step 2 で uninitialized なら ReferenceError。これが super() 前に this
読むと ReferenceError になる正体です。「this が undefined になる」のでも
「TDZ に入っている」のでもなく、仕様レベルでは [[ThisBindingStatus]]
スロットが uninitialized 状態である
ことが理由です。

super()BindThisValue を呼ぶ

super()(SuperCall)は、親クラスのコンストラクタを呼んで戻ってきたオブジェクトを
[[ThisValue]] にセットします。

原文:
SuperCall : super Arguments

  1. Let newTarget be GetNewTarget().
  2. Assert: newTarget is a constructor.
  3. Let superConstructor be GetSuperConstructor().
  4. Let argList be ? ArgumentListEvaluation of Arguments.
  5. If IsConstructor(superConstructor) is false, throw a TypeError exception.
  6. Let result be ? Construct(superConstructor, argList, newTarget).
  7. Let thisER be GetThisEnvironment().
  8. Assert: thisER is a Function Environment Record.
  9. Perform ? BindThisValue(thisER, result).
  10. Let funcObj be thisER.[[FunctionObject]].
  11. Assert: funcObj is an ECMAScript function object.
  12. Perform ? InitializeInstanceElements(result, funcObj).
  13. Return result.
    §13.3.7.1 SuperCall: Runtime Semantics: Evaluation

step 9 の BindThisValue が、derived class の Function ER に
親コンストラクタが返した新オブジェクトを書き込みます。これで
[[ThisBindingStatus]] = initialized になり、以降 this が使えるようになります。

ちなみに BindThisValue の step 2 を再掲します。

原文:

  1. Assert: envRec.[[ThisBindingStatus]] is not lexical.
  2. If envRec.[[ThisBindingStatus]] is initialized, throw a ReferenceError exception.
    ...
    §9.1.1.3.1 BindThisValue

initialized 状態の Function ER にもう一度 BindThisValue を呼ぶと
ReferenceError。これが super() を2回呼ぶと ReferenceError になる理由です。

construct_uninitialized.png

[[ThisBindingStatus]] 3状態のまとめ

状態 いつ this 式の挙動
lexical Arrow Function の Function ER(5章) HasThisBinding が false → 外側の ER に走査が伸びる
uninitialized derived constructor 本体開始時、super() GetThisBinding が ReferenceError
initialized 通常関数の本体内、base コンストラクタ本体内、derived コンストラクタの super() [[ThisValue]] を返す

この章のまとめ

new[[Call]] とは別の経路 [[Construct]] を通り、this の初期化
タイミングが base / derived で異なる
。base では本体開始時に既に
[[ThisValue]] が書き込まれているのに対し、derived では super() までは
[[ThisBindingStatus] = uninitialized のまま。だから super() 前に this
読むと ReferenceError になる。

ここまでで this を決定する仕組みのすべての経路を見ました。次章で1章の謎に
戻ります。


8. 1章の謎の解明

ここまでの章で部品はすべて揃いました。1章のコードに戻ります。

"use strict";

const obj = {
  name: "obj",
  who() { return this?.name ?? "(no this)"; },
};

const f1 = () => obj.who();
const f2 = () => { const g = obj.who; return g(); };
const f3 = () => obj.who.call({ name: "X" });

f1(); // "obj"
f2(); // "(no this)"
f3(); // "X"

3つのケースに共通するのは、同じ関数オブジェクト obj.who を呼んでいることと、
その関数オブジェクトの [[ThisMode]] = strict であることです。

ch8_step0_setup.png

違うのは、[[Call]]thisArgument 引数に何が入るか だけです。

f1: obj.who() の場合

ch8_step1_dotcall.png

  1. obj.who を評価 → Reference Record([[Base]] = obj[[ReferencedName]] = "who"
  2. EvaluateCall がこの Reference Record を受け取り、IsPropertyReference が true なので
    thisValue = GetThisValue(ref) = obj
  3. Call(who, obj, [])who.[[Call]](obj, [])
  4. PrepareForOrdinaryCall で新 EC・新 Function ER を作る
  5. OrdinaryCallBindThis: [[ThisMode]] = strict なので step 5 に進み、
    thisValue = objBindThisValue(obj) で Function ER の [[ThisValue]] = obj
  6. 本体内 thisResolveThisBindingobjobj.name = "obj" を返す

再掲(原文):

  1. If ref is a Reference Record, then
    a. If IsPropertyReference(ref) is true, then
    i. Let thisValue be GetThisValue(ref).
    §13.3.6.2 EvaluateCall

f2: const g = obj.who; g() の場合

ch8_step2_assigned.png

  1. const g = obj.who: 右辺の Reference Record は GetValue で値化 され、
    g には関数オブジェクト who だけが入る。[[Base]] = obj の情報はここで消える
  2. g() の評価: g という識別子の Reference Record ができる
    [[Base]] は識別子が宣言された ER)
  3. EvaluateCall がこの Reference を受け取る。IsPropertyReferencefalse
    (プロパティ参照ではなく識別子参照)なので、step 1.b に進む:
    thisValue = refEnv.WithBaseObject()
  4. 普通の Environment Record(Declarative ER / Function ER / Module ER)の
    WithBaseObject は常に undefined を返す(with 文専用の Object ER のみ
    オブジェクトを返す)
  5. Call(who, undefined, [])OrdinaryCallBindThis[[ThisMode]] = strict なので
    thisValue = undefined がそのまま使われる
  6. 本体内 thisundefinedundefined?.name ?? "(no this)""(no this)"

原文(参考):

  1. Return undefined.
    §9.1.1.1.11 Declarative Environment Records: WithBaseObject

ポイントは、g に代入した時点で obj との結びつきが消える」 こと。これが
3章で見た「Reference は GetValue で値に潰れる」の具体例です。

f3: obj.who.call({ name: "X" }) の場合

ch8_step3_call.png

  1. obj.who.call を評価 → Reference Record([[Base]] = obj.who[[ReferencedName]] = "call"
  2. EvaluateCallthisValue = obj.who(Function.prototype.call の this)、
    args = [{name:"X"}]
  3. Function.prototype.call の本体(§20.2.3.3):
    • step 1: func = this value = obj.who
    • step 4: Call(func, thisArg, args) = Call(obj.who, {name:"X"}, [])
  4. who.[[Call]]({name:"X"}, [])OrdinaryCallBindThisthisValue = {name:"X"}
  5. 本体内 this{name:"X"}"X" を返す

3章の Reference の経路を 1段スキップしてFunction.prototype.call 内の
Call(func, thisArg, ...)thisArgument を明示している、というのが
仕様レベルでの説明です。

3つを一望する

呼び出し thisArgument の出どころ Function ER [[ThisValue]] this?.name
f1() Reference の [[Base]] = obj obj "obj"
f2() WithBaseObject() = undefined undefined (no this)
f3() Function.prototype.call の第1引数 {name:"X"} "X"

「同じ関数なのに this が変わる」のではなく、[[Call]]thisArgument 引数に
何が渡るかが、呼び出し式の形によって違う」
だけです。this 自体の決め方は
常に同じ: OrdinaryCallBindThisthisArgument を加工せず(strict なら)
Function ER の [[ThisValue]] に書き込み、this 式がそれを引く。

Arrow Function 例も再確認

1章の outer.arrow() も同じ枠組みで説明できます:

  • outer.arrow → Reference Record([[Base]] = outer
  • EvaluateCallthisValue = outer
  • Call(arrow, outer, [])arrow.[[Call]](outer, [])
  • OrdinaryCallBindThis: [[ThisMode]] = lexical なので step 2 で即 return
  • Function ER の [[ThisValue]]書き込まれない[[ThisBindingStatus]] = lexical
  • 本体内 thisResolveThisBindingGetThisEnvironment がこの Function ER を
    スキップして外側 ER(モジュールトップ)へ → undefined(no this)

outer という情報は OrdinaryCallBindThis の step 2 で完全に捨てられる
ドットで呼ぼうが call で渡そうが、Arrow Function には届きません。

この章のまとめ

this は呼び方で変わる」のではなく、「呼び出し式が何を [[Call]]
thisArgument として渡すか」が呼び方で違うだけ
。そして OrdinaryCallBindThis
thisArgument を Function ER の [[ThisValue]] に書き込み、this 式は
それを引く、という骨組みは常に同じ。Arrow Function はこの書き込み処理自体を
スキップし、derived constructor は super() まで書き込みを遅延する。
バリエーションは存在するが、すべて同じ仕組みの上に乗っている。


9. まとめ

本記事の内容を4項目にまとめます。

  1. this 式の値は、ResolveThisBinding が見つけた一番内側の Function ER(または
    Global ER)の [[ThisValue]] スロットに格納されている値
    this は変数では
    なくスロット参照。

  2. [[ThisValue]] の中身は OrdinaryCallBindThisthisArgument から書き込む
    その thisArgument の出どころは:

    • ドット呼び出し obj.f() → Reference Record の [[Base]] = obj
    • 裸の呼び出し g() → 識別子参照の WithBaseObject() = undefined(strict)
    • call/apply → ユーザー指定の第1引数
    • bindBoundFunctionExoticObject.[[BoundThis]]
    • new → base なら新規オブジェクト、derived なら super() 経由で親から戻ったオブジェクト
  3. Arrow Function は [[ThisMode]] = lexicalOrdinaryCallBindThis を早期 return する。
    結果、Function ER の [[ThisValue]] は書き込まれず、HasThisBinding
    false を返すため、this 式は外側の ER まで走査が伸びる。「自分の this
    持たない」とはこの早期 return のこと。

  4. derived class(extends あり)の constructor は super() 前に
    [[ThisBindingStatus]] = uninitialized
    GetThisBinding がこの状態を見ると
    ReferenceErrorsuper()BindThisValue で初期化することで this
    解禁される。

this を「呼び方によって値が変わる魔法の変数」と捉えていた方は、ぜひ
「Function ER の [[ThisValue]] スロットに OrdinaryCallBindThis が書き込む値」
と読み替えてみてください。これだけで仕様書のあちこちの記述が一直線に繋がります。



参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?