【STEP.33】
ブラウザーオブジェクトについて徹底解説!

ブラウザーオブジェクトとは

ブラウザーオブジェクトとは、ブラウザー操作のための機能を集めたオブジェクト群の総称です。

Google ChromeやInternet Explorerなど、ほとんどのブラウザーで昔から実装されているものですが、以前はこれといった標準の規格が存在するわけではありませんでした。

そのため、過去はクロスブラウザー問題(=ブラウザーごとの仕様差によって発生する問題)が取りざたされることも多かったですが、昨今では標準化が進んで、かなり解消されつつあります。

このページでは、あまたあるブラウザーオブジェクトの中でも特に重要な機能に絞って解説を進めます。

ブラウザーオブジェクトの階層構造

ブラウザー環境で提供されるオブジェクトは、次のようなツリー構造を採ります。

主要なブラウザーオブジェクト

ブラウザー環境ですべてのオブジェクトの最上位に位置するのがWindowオブジェクトです。

JavaScriptが起動するタイミングで自動的に生成され、グローバル変数/関数を管理するための便宜的なオブジェクトです。(グローバルオブジェクトともいいます。)

あくまで形式的なものなので、

let w = new Window();

のように、明示的にインスタンス化することもできませんし、

Window.メソッド名(...);

のように、配下のメンバーを呼び出せるわけでもありません。

グローバル変数hoge、グローバル関数foo

console.log(hoge);
foo();

のように表せるのと同じように、Windowオブジェクトのメンバーも

alert('あいうえお');

のように表記します。

もっとも、以下のような書き方もあります。

window.alert('Anything');

ただし、この「window.」は(グローバルオブジェクトそのものではなく)Windowオブジェクトのプロパティで、自分自身を参照します。

window.~」で明示的にグローバルオブジェクトを取得し、そのメソッドであるalertにアクセスしているわけです。

同じように、以下のような書き方も間違いではありません。

window.console.log('Anything'); ブラウザーオブジェクトの参照
console.log(window.hoge); グローバル変数へのアクセス
window.foo(); グローバル関数の呼び出し

上記のツリー図を見てもわかるように、consoledocumenthistoryなどはすべてWindowオブジェクトのプロパティです。(consoleオブジェクトは、正しくは、「Consoleオブジェクトを参照するconsoleプロパティ」なのです。)

よって、「window.~」経由でのアクセスが可能です。(window.console.log('Anything');

同じく、グローバル変数(console.log(window.hoge); )、グローバル関数(window.foo(); )も、グローバルオブジェクトのプロパティ/メソッドであると考えれば、「window.」経由でアクセスしてもかまわないわけです。

しかし、これらは意味がないうえ冗長なだけなので、一般的には省略して表します。

確認ダイアログを表示する【confirm】

confirmメソッドを利用します。

たとえば以下は、[ページを移動]ボタンをクリックすると、確認ダイアログを表示し、[OK]ボタンを押した場合にだけページを移動します。

<form>
  <input id="btn" type="button" value="ページを移動" />
</form>
document.getElementById('btn').addEventListener('click', function (e) {
  if (window.confirm('ページを移動しても良いですか?')) {
    location.href = 'https://yutamanshop.com/';	
  }
}, false);

確認ダイアログ

confirmメソッドは、押されたボタンによって、次に掲げる表のような戻り値を返します。

【confirmメソッドの戻り値】
ボタン概要
[OK]true
[キャンセル]false

上のJavaScriptのコード2~4行目では、confirmメソッドのこの性質を利用して、[OK]ボタンが押された場合にだけlocation.hrefプロパティを呼び出しています。([キャンセル]ボタンを押した場合には何もしません。)

confirmメソッドは、フォームのサブミット時に送信の可否を確認する用途でもよく利用します。

<form id="myform">
  <input id="btn" type="submit" value="送信" />
</form>
document.getElementById('myform').addEventListener('submit', function (e) {
  if (!window.confirm('送信しても良いですか?')) {
    e.preventDefault();	
  }
}, false);

確認ダイアログ

この例であれば、[キャンセル]ボタンがクリックされた場合に、preventDefalutメソッドを呼び出すことで、submitイベント本来の挙動(=フォームを送信する)を取り消しているわけです。

一定時間のあとで処理を実行する【setTimeout】

setTimeoutメソッドを利用します。

setTimeoutメソッド

setTimeout(func, delay [, params, ...])
func    実行すべき任意の処理
delay   何ミリ秒後に処理を実行するか
params  引数funcに引き渡す引数(可変長引数)

delayミリ秒のあと、引数funcを実施します。

戻り値は、タイマーを識別するためのid値です。

id値はclearTimeoutメソッドに渡すことで、タイマーを停止できます。

たとえば以下は、[スタート]ボタンクリック後、現在時刻をダイアログ表示する例です。

[ストップ]ボタンをクリックした場合には、処理は停止します。

<button id="start">スタート</button>
<button id="stop">ストップ</button>
let timer;
	
// [スタート]ボタンを押して10秒後にダイアログを表示
document.getElementById('start').addEventListener('click', function (e) {
  timer = setTimeout(function () {
    window.alert(new Date());
  }, 10000);
}, false);

// [ストップ]ボタンでタイマーを停止
document.getElementById('stop').addEventListener('click', function (e) {
  clearTimeout(timer);
}, false);

引数付きの関数を実行する場合

引数paramsを利用することで、引数funcに引数を渡すこともできます。

たとえば、以下は1000/8000ミリ秒後に、引数で指定されたメッセージを表示する例です。

let handler = function (message) {
  console.log(message);
};
	
setTimeout(handler, 1000, 'こんにちは!');
setTimeout(handler, 8000, 'さようなら...');

setTimeoutメソッドを利用する上での注意点

setTimeoutはシンプルなメソッドですが、それだけに利用に際しては注意すべき点もあります。

以下に、主なものをまとめておきます。

引数funcを文字列で指定しない

以下のようなコードは構文上は許されていますが、望ましくありません。

setTimeout('window.alert(new Date());', 10000);

文字列を解析しなければならず、処理効率が悪いためです。

また、外部の入力に基づいてコードを組み立てている場合、任意の処理を実行できてしまうため、脆弱性の原因にもなりやすいという問題もあります。

処理は、待ち行列(キュー)に登録される

厳密には、setTimeoutメソッドは指定された時間に処理を実行するわけではありません。

指定された時間に、キュー(待ち行列)に、処理を登録するだけです。

よって、キューに実行待ちの処理が残っている場合には、先行する処理を終えるまで待たなければなりません。(その時間で処理が実行されることを保証するものではありません。)

setTimeoutメソッドは実行時間を保証しない

MEMO
シングルスレッド
あくまで処理は1つのスレッドの上で順番に実行されるということです。(これをシングルスレッド処理といいます。)

複数のスレッドで並行して処理を実行するには、Web Workerを利用します。

引数delayにゼロを指定した場合

たとえば以下のようなコードを想定してみましょう。

console.log('one');
setTimeout(function () {
  console.log('Two');
}, 0);
console.log('Three');

ソースコード2~4行目は0ミリ秒後に実行されるので、OneTwoThreeの順でログが出力されるように見えます。

しかし、結果はOneThreeTwoの順です。

setTimeoutメソッドで処理をキューに送っている間に、JavaScriptはまずはメインの処理を実施し、そのうえでキューを処理するわけです。

このような処理を非同期処理といいます。

これを利用したテクニックが以下です。

setTimeout(function () {
  // なんらかの重い処理
}, 0);
// 後続の処理

「重い処理」をsetTimeoutを使わずにそのまま呼び出した場合には、「後続の処理」は「重い処理」の終了を待たなければなりません。

しかし、setTimeoutを介することで、「重い処理」はいったんキューに退避され、後続の処理が先に実行されるので、体感速度を改善できます。

4ミリ秒の制約とは

正確には、タイマーには最小遅延の制約が定められています。

連続してコールバックが呼び出された場合、タイマーは最低でも4ms間隔で呼び出すように制限を課します。

非同期処理を目的として、いわゆる0msタイマーを用いる場合には、setTimeoutメソッドよりも、postMessageメソッドを利用すべきです。

window.postMessage(msg, '*');

一定時間おきに処理を実行する【setInterval】

一定時間おきに処理を実行したい場合には、setIntervalメソッドを利用します。

setTimeoutメソッドとも似ていますが、setTimeoutメソッドが時間経過後に指定の処理を一度だけ実行するのに対して、setIntervalメソッドは何度も実行する点が異なります。

戻り値はタイマーidで、これをclearIntervalメソッドに渡すことで、タイマーを停止できます。(構文を含めて、この辺の考え方はsetTimeoutメソッドと同じです。)

setIntervalメソッド

setInterval(func, delay)
func    実行すべき任意の処理
delay   何ミリ秒ごとに処理を実行するか

たとえば以下は、1000ミリ秒おきに現在時刻をページに反映する例です。

<button id="start">スタート</button>
<button id="stop">ストップ</button>
<div id="result"></div>
let timer;
	
// [スタート]ボタンを押すと1秒おきに時刻の表示を更新
document.getElementById('start').addEventListener('click', function (e) {
  timer = setInterval(function () {
    document.getElementById('result').textContent = new Date();
  }, 1000);
}, false);
	
// [ストップ]ボタンでタイマーを停止
document.getElementById('stop').addEventListener('click', function (e) {
  clearInterval(timer);
},false);

確認ダイアログ

setIntervalメソッドを利用する場合はブラウザーのクラッシュに注意

ただし、setIntervalメソッドで実行間隔よりも処理時間が長い場合、問題が生じる可能性があります。

setInterval(function () {
  // 平均して1000ミリ秒以上かかる処理
}, 1000);

実行待ちのキューがさばけていかずに、処理だけがたまっていくので、ブラウザーがクラッシュする可能性があるのです。

このような状況では、setTimeoutメソッドを利用すべきです。

(function timer() {
  setTimeout(function () {
    // 1000ミリ秒単位で実行すべき処理
    timer();
  }, 1000);
})();

timer関数は、いわゆる即時関数で、定義して即座に実行します。

timer関数は、setTimeout配下で再帰的に呼び出されるので、結果、1000ミリ秒スパンで処理が実行されることになります。

setIntervalメソッドとの違いは、setIntervalメソッドが先行する処理の終了を待たないのに対して、timer関数は、待つ点です。(現在の処理が終わらなければ、次のtimer関数は呼び出されません。)

それによって、キューのあふれを防いでいるわけです。

ウィンドウサイズを取得する【innerHeight/innerWidth/outerHeight/outerWidth】

Windowオブジェクトは、ウィンドウサイズに関する次に掲げる表のようなプロパティを提供しています。

【ウィンドウサイズに関わるプロパティ】
プロパティ概要
innerHeight表示領域の高さ
innerWidth表示領域の幅
outerHeightブラウザー外側の高さ
outerWidthブラウザー外側の幅

以下は、それぞれの値を確認するためのコードです。(その時どきのウィンドウサイズによって変化します。)

console.log('表示領域の高さ:' + window.innerHeight);
console.log('表示領域の幅:' + window.innerWidth);
console.log('ブラウザー外側の高さ:' + window.outerHeight);
console.log('ブラウザー外側の幅:' + window.outerWidth);
表示領域の高さ:95
表示領域の幅:680
ブラウザー外側の高さ:444
ブラウザー外側の幅:691

ページを指定位置までスクロールする【scrollTo】

scrollToメソッドを利用します。

<div class="desc">
  <div id="chap1">
    <h2>はしがき</h2>
    <p>私は、その男の写真を三葉、見たことがある。...中略...</p>
  </div>
  <div id="chap2">
    <h2>第一の手記</h2>
    <p>恥の多い生涯を送って来ました。...中略...</p>
  </div>
</div>
document.getElementById('btn').addEventListener('click', function (e) {
  window.scrollTo(50,150);
}, false);

スクロール時にアニメーションする場合【behaviorオプション】

scrollToメソッドの既定では、即座に指定の位置にスクロールします。

しかし、behaviorオプションでsmoothを渡すことで、いくらかの時間をかけて滑らかにスクロールするようになります。

window.scrolTo({
  top: 50,
  left:  150,
  behavior: 'smooth'
});

behaviorオプションを指定する場合には、X/Y座標もそれぞれtopleftオプションで渡します。

ページを指定量だけスクロールする【scrollBy】

scrollByメソッドを利用します。

たとえば以下は、ページの任意の場所をクリックするたびに、1ページ単位にスクロールしていく例です。

<div class="desc">
  <div id="chap1">
    <h2>はしがき</h2>
    <p>私は、その男の写真を三葉、見たことがある。...中略...</p>
  </div>
  <div id="chap2">
    <h2>第一の手記</h2>
    <p>恥の多い生涯を送って来ました。...中略...</p>
  </div>
</div>
document.addEventListener('click', function (e) {
  window.scrollBy(0, window.innerHeight);
}, false);

innerHeightプロパティは、ウィンドウの表示領域の高さを表すので、「window.scrollBy(0, window.innerHeight);」でクリックのたびに表示サイズ(ページ)だけを縦スクロールする、という意味になります。

特定の要素までページをスクロールする【scrollIntoView】

scrollIntoViewメソッドを利用します。

たとえば以下は、[第2章に移動]ボタンで<div id=”chap2″>要素に移動する例です。

<button id="move">第2章に移動</button>
<div class="desc">
  <div id="chap1">
    <h2>はしがき</h2>
    <p>私は、その男の写真を三葉、見たことがある。...中略...</p>
  </div>
  <div id="chap2">
    <h2>第一の手記</h2>
    <p>恥の多い生涯を送って来ました。...中略...</p>
  </div>
</div>
let move = document.getElementById('move');
let c2 = document.getElementById('chap2');
	
// ボタンクリックで次章にスクロール
move.addEventListener('click', function () {
  c2.scrollIntoView(true);
}, false);

引数のtrueは要素が表示領域の上端に来るようにスクロールすることを意味します。(既定なので、省略してもかまいません。)

falseとした場合には、要素の下端が表示領域の下端になるようにスクロールします。

ただし、たとえば要素がページの末尾にあるなどで、スクロールしきれない場合は、可能な範囲までスクロールします。(常に、上端/下端になるまでスクロールするわけではありません。)


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です