1.

JavaScript「Illegal invocation」エラーの原因と対処(this 喪失 / bind / アロー関数)

編集
この記事の要点
  • Uncaught TypeError: Illegal invocationDOM / Web API のメソッドを別オブジェクトに代入して呼び出したとき発生
  • 代表例: const fn = document.querySelector; fn("div") → this が document でなく undefined になりエラー
  • 対処1: fn.bind(document) で this を固定
  • 対処2: アロー関数で包む const fn = (s) => document.querySelector(s)
  • 対処3: 元の document.querySelector("div") をそのまま使う(最も単純)

このエラーが出る典型例

// (1) DOM API を短く書こうとして this を失う
const $ = document.querySelector;
$('div');
// Uncaught TypeError: Illegal invocation

// (2) console.log を別名に代入して呼ぶ
const log = console.log;
log('hello');
// Chrome/Firefox では動くが、古いブラウザでエラー

// (3) setTimeout に直接 method を渡す
setTimeout(document.querySelector, 100, 'div');
// → Illegal invocation

// (4) Array.forEach で this を渡し忘れる
[document.body].forEach(el => el.appendChild);
// 呼出時に Illegal invocation

原因: this の喪失

document.querySelector のような ネイティブメソッドは、内部で thisHTMLDocument のインスタンスであることを期待しています。別の変数に代入して呼ぶと、this が undefined(strict mode)や window(非 strict)になるため、ネイティブコードが「この呼び出し方は不正だ」と判定して投げるエラーが Illegal invocation です。

// 内部実装イメージ(疑似コード)
HTMLDocument.prototype.querySelector = function(selector) {
  if (!(this instanceof HTMLDocument)) {
    throw new TypeError('Illegal invocation');
  }
  // ...
};

// だから:
document.querySelector('div');  // this = document → OK
const fn = document.querySelector;
fn('div');                      // this = undefined/window → エラー

対処1: bind で this を固定

// bind 第1引数に this を渡す
const $ = document.querySelector.bind(document);
$('div');   // OK

const $$ = document.querySelectorAll.bind(document);
$$('p');    // OK

// console.log も同じ
const log = console.log.bind(console);
log('hello');  // OK

対処2: アロー関数で包む

// アロー関数の中なら this が変わらない
const $ = (sel) => document.querySelector(sel);
$('div');   // OK

const $$ = (sel) => document.querySelectorAll(sel);

// 引数を受け流す ...rest
const log = (...args) => console.log(...args);
log('hello', 'world');

対処3: call / apply で呼ぶ

const fn = document.querySelector;

// call: 引数を個別に
fn.call(document, 'div');

// apply: 引数を配列で
fn.apply(document, ['div']);

対処4: setTimeout に渡すとき

// NG
setTimeout(document.querySelector, 100, 'div');

// OK: bind
setTimeout(document.querySelector.bind(document), 100, 'div');

// OK: アロー関数で包む(推奨)
setTimeout(() => document.querySelector('div'), 100);

jQuery で見るパターン

// jQuery でラップしたものをネイティブ呼出
const $div = $('#myDiv');
$div.click;       // 関数オブジェクトだが、呼び出すと this 喪失する場合がある

// イベントハンドラ内で this を失う
$('#btn').on('click', function() {
  setTimeout(this.appendChild, 0, somenode);
  // → Illegal invocation (this が要素ではなく window に)

  // 修正: bind か アロー
  setTimeout(this.appendChild.bind(this), 0, somenode);
  setTimeout(() => this.appendChild(somenode), 0);
});

FormData / fetch でも出る

// よくあるアンチパターン
const formData = new FormData();
const append = formData.append;
append('key', 'value');
// Illegal invocation

// 修正
const append = formData.append.bind(formData);
// または
formData.append('key', 'value');

エラーが出やすい API 一覧

API必要な this
document.querySelector / querySelectorAlldocument
document.createElementdocument
console.log / error / warnconsole(一部ブラウザ)
FormData.appendFormData インスタンス
Headers.append / set / getHeaders インスタンス
URL.searchParams.getURLSearchParams
localStorage.getItemStorage
HTMLElement.appendChildHTMLElement
WebAudio API 全般各 Node

デバッグの手順

  1. スタックトレースで、エラー発生行のメソッド名を見つける
  2. そのメソッドが ネイティブの DOM/Web API か確認
  3. 呼び出し直前で this がどうなっているか debuggerconsole.log(this) で確認
  4. this が undefined / window / 別のオブジェクトなら、bind か アロー関数で修正

FAQ

Q: ES2022 以降では出なくなる?
A: 仕様自体は変わりません。this 喪失の問題は構造的なものなので、bind / アローで対処する以外ありません。

Q: TypeScript で防げないか
A: strictBindCallApplynoImplicitThis を有効にすると、代入時に this 不整合を型エラーで検出できる場合があります。

Q: console.log は今や普通に代入できる
A: モダンブラウザでは緩和されています。古い IE や一部の Edge では Illegal invocation が出るため、レガシー対応が必要なら bind 推奨。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. Uncaught TypeError: Illegal invocation
  2. Form submission canceled because the form is not connected
  3. Uncaught TypeError: location.href is not a function
  4. Access to XMLHttpRequest at 'url1' from origin 'url2' has been blocked
  5. Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')