このページはコミュニティーの尽力で英語から翻訳されました。MDN Web Docs コミュニティーについてもっと知り、仲間になるにはこちらから。

View in English Always switch to English

new.target

Baseline 広く利用可能

この機能は広く実装されており、多くのバージョンの端末やブラウザーで動作します。2017年9月以降、すべてのブラウザーで利用可能です。

new.target はメタプロパティで、関数やコンストラクターが new 演算子を使用して呼び出されたかどうかを検出することができます。 new 演算子を使用して呼び出したコンストラクターや 関数の中では、 new.targetnew が呼び出されたコンストラクターまたは関数への参照を返します。通常に呼び出された関数の中では、 new.targetundefined になります。

試してみましょう

function Foo() {
  if (!new.target) {
    throw new TypeError("new を付けずに Foo コンストラクターを呼び出すのは不正です");
  }
}

try {
  Foo();
} catch (e) {
  console.log(e);
  // 予想される結果: TypeError: new を付けずに Foo コンストラクターを呼び出すのは不正です
}

構文

js
new.target

new.target は、構築可能な関数値または undefined であることが保証されています。

  • クラスのコンストラクター内では、 new が呼び出されたクラスを参照します。これは現在のコンストラクターのサブクラスである可能性があります。サブクラスは super() を通じてスーパークラスのコンストラクターを経過的に呼び出すためです。
  • 通常の関数では、関数が new で直接構築された場合、 new.target はその関数自体を参照します。関数が new なしで呼び出された場合、new.targetundefined になります。関数は extends の基底クラスとして使用されることがあり、その場合 new.target はサブクラスを参照する可能性があります。
  • コンストラクター(クラスまたは関数)が Reflect.construct() 経由で呼び出された場合、 new.targetnewTarget として渡された値(既定は target)を参照します。
  • アロー関数では、 new.target は周囲のスコープから継承されます。アロー関数が new.targetバインディングを持つ別のクラスや関数内で定義されていない場合、構文エラーが発生します。
  • 静的初期化ブロック内では、 new.targetundefined です。

解説

new.target 構文は、キーワード new とドットと target 識別子で構成されています。new は識別子ではなく予約語であるため、これはプロパティアクセサーではなく、特別な式構文です。

new.target メタプロパティは、すべての関数/クラスの本体内で利用できます。関数やクラス の外部で new.target を使用すると構文エラーになります。

関数呼び出しにおける new.target の使用

通常の関数呼び出しでは (コンストラクター関数の呼び出しとは対照的に)、 new.targetundefined になります。これにより、関数が new 付きでコンストラクターとして呼び出されたかを検出できます。

js
function Foo() {
  if (!new.target) {
    throw new Error("Foo() は new を付けて呼び出さなくてはなりません");
  }
  console.log("Foo が new 付きでインスタンス化されました");
}

new Foo(); // "Foo が new 付きでインスタンス化されました" を出力
Foo(); // "Foo() は new を付けて呼び出さなくてはなりません" 例外が発生

コンストラクターにおける new.target

クラスのコンストラクターでは、new.targetnew で直接実行されたコンストラクターを参照します。これは、コンストラクターが親クラスにあり、子コンストラクターから委任された場合も同様です。new.target は、new が呼び出されたクラスを指します。例えば、bnew B() を使用して初期化された際には、B の名前が表示されます。同様に、a の場合、クラス A の名前が表示されます。

js
class A {
  constructor() {
    console.log(new.target.name);
  }
}

class B extends A {
  constructor() {
    super();
  }
}

const a = new A(); // Logs "A"
const b = new B(); // Logs "B"

Reflect.construct() を使用したときの new.target

Reflect.construct() やクラスが登場する以前は、継承を実装する一般的な方法として、this の値を渡し、基底クラスのコンストラクターでそれを変更させる手法が用いられていました。

js
function Base() {
  this.name = "Base";
}

function Extended() {
  // Base() コンストラクターが、 `new` によって生成される新しいオブジェクト
  // ではなく、既存の `this` 値に対して動作する唯一の 方法。
  Base.call(this);
  this.otherProperty = "Extended";
}

Object.setPrototypeOf(Extended.prototype, Base.prototype);
Object.setPrototypeOf(Extended, Base);

console.log(new Extended()); // Extended { name: 'Base', otherProperty: 'Extended' }

ただし、call() および apply() は実際には関数を「構築」するのではなく「呼び出し」するため、new.target の値は undefined になります。これは、Base()new で構築されたかどうかを確認する場合、エラーが発生するか、それ以外にも予期しない動作を引き起こす可能性があるということの意味します。例えば、Map() コンストラクターは new なしでは呼び出せないため、この方法で Map を拡張することはできません。

すべての組み込みコンストラクターは、 new.target.prototype を読み取ることで、新規インスタンスのプロトタイプチェーン全体を直接構築します。したがって、(1) Basenew で構築され、(2) new.targetBase 自体ではなくサブクラスを指すようにするには、 Reflect.construct() を使用する必要があります。

js
function BetterMap(entries) {
  // 基底クラスのコンストラクターを呼び出すが、`new.target` をサブクラスに設定する。
  // これにより、作成されるインスタンスに正しいプロトタイプチェーンが構築される。
  return Reflect.construct(Map, [entries], BetterMap);
}

BetterMap.prototype.upsert = function (key, actions) {
  if (this.has(key)) {
    this.set(key, actions.update(this.get(key)));
  } else {
    this.set(key, actions.insert());
  }
};

Object.setPrototypeOf(BetterMap.prototype, Map.prototype);
Object.setPrototypeOf(BetterMap, Map);

const map = new BetterMap([["a", 1]]);
map.upsert("a", {
  update: (value) => value + 1,
  insert: () => 1,
});
console.log(map.get("a")); // 2

メモ: 実際、 Reflect.construct() が存在しないため、 ES6 以前のコードへトランスパイルする際には、組み込みオブジェクトを正しくサブクラス化することができません(Error のサブクラス化など)。

ただし、 ES6 のコードを書く場合は、読み取り可能でエラーの可能性が低いクラスと extends の使用を推奨します。

js
class BetterMap extends Map {
  // コンストラクターは既定のコンストラクターのみであるため省略

  upsert(key, actions) {
    if (this.has(key)) {
      this.set(key, actions.update(this.get(key)));
    } else {
      this.set(key, actions.insert());
    }
  }
}

const map = new BetterMap([["a", 1]]);
map.upsert("a", {
  update: (value) => value + 1,
  insert: () => 1,
});
console.log(map.get("a")); // 2

仕様書

仕様書
ECMAScript® 2027 Language Specification
# sec-built-in-function-objects

ブラウザーの互換性

関連情報