window.onload の処理を連結する方法

いや、素直に window.addEventListener('load') 使えば良いんですけども。やり方を考えてみたかった、というだけで。

window.onload = function() {}; という形で、ページ読み込み時に行いたい処理を宣言する手法が昔はよく使われていた。最近のブラウザは window.addEventListener('load') なり document.addEventListener('DOMContentLoaded') なりが使えるので、後から処理を追加する ということも容易だが、window.onload = … の場合は代入しているので、ココに後から処理を追加するにはどうしたらいいかなと考えていた。

よくよく考えると、何もスクリプトを書いていない場合は、window.onload の値は null になっている。代入して初めて値を持つのだ。つまり、後から処理を追加したい関数の中で存在チェックしてやれば、事前に window.onload に代入した処理を実行してから次の処理を実行できるんじゃないかと考えた。

window.onload = function() {
  // 最初の処理を設定する
};

// 最初の処理の後に何か処理を追加したい場合

// 現時点の window.onload の内容を控えておく
var previousFunction = window.onload;
// 処理全体は window.onload に再代入する
window.onload = function() {
  // 直前の window.onload に値があればその関数を実行する
  if(previousFunction) {
    previousFunction();
  }
  
  // 追加したい処理を記述する…
};

しかし、このやり方は1回しか処理を追加できない。同じ構成で更に関数を追加しようとすると、直前の関数の呼び出しが無限に繰り返されてしまうのだ (いわゆるスタックオーバーフロー)。

ではどうするかというと、以下のような extend 関数を自前で定義して使う方法が紹介されていた。

Function.prototype.extend = function(fn) {
  var self = this;
  return function() {
    self.apply(this, arguments);
    fn.apply(this, arguments);
  };
};

呼び出す側は以下のようにする。

// 一番最初は window.onload は null なので普通に代入する
window.onload = function() {
  console.log('First');
};

// 2回目は extend を使い引数に追加実行したい関数を渡す
window.onload = window.onload.extend(function() {
  console.log('Second');
});

// 3回目以降も同じ
window.onload = window.onload.extend(function() {
  console.log('Third');
});

こうすることで、合計3つの関数を追加した順に実行することができた。

関数をディープコピーできないかとか考えていたのだけど、これが安定していそう。関数の連結が文字列結合みたく += とかできたらいいのになぁ〜。Promise.then() はそういうチェーンができるけど、window.onload は関数にしてやらないとダメだろうし〜。というワケでこんなやり方。