【STEP.35】
location・history・navigatorオブジェクトについて徹底解説!

locationオブジェクト

locationオブジェクトとは

たとえばボタンをクリックすることで、別ページに移動したり、現在のページをリロードしたいという状況はよくあります。

そのようなケースで利用するのが、locationオブジェクトです。

locatonオブジェクトには、次に掲げる表のようなメンバーが用意されています。

戻り値の例は、現在のURLが「https://yutamanshop.com:8080/utask/source-code.html?id=123#se」であることを前提に示しています。

【locationオブジェクトの主なメンバー】
メンバー概要戻り値(例)
hashアンカー名(#~)#se
hostホスト(ホスト名+ポート番号)saruwakakunn.com:8080
hostNameホスト名saruwakakunn.com
hrefリンク先https://yutamanshop.com:8080/utask/source-code.html?id=123#se
pathnameパス名utask/source-code.html
portポート番号8080
protocolプロトコル名https:
searchクエリ情報?id=123
reload()現在のページを再読み込み
replace(url)指定ページurlに移動

ページを移動する【location.hrefプロパティ】

location.hrefプロパティに、移動先のページを指定します。

<button id="btn">クリック</button>
document.getElementById('btn').addEventListener('click', function (e) {
  location.href = "linked.html";
},false);

ちなみに、hrefプロパティはassignメソッドに置き換えても同じ意味です。

たとえば、以下のコードは「location.href = "linked.html";」と等価です。

location.assign('linked.html');

履歴を加えずにページを移動する【replace】

hrefプロパティ/assignメソッドによく似たメソッドとして、replaceメソッドがあります。

引数に指定されたページに移動するという点で、見かけ上同じ挙動をしますが、replaceメソッドは前のページを履歴に残さないという点でことなります。(つまり、ブラウザーの[戻る]ボタンを使っても、移動前のページに戻れないということです。)

location.replace('linked.html');

クエリ情報を取得する【searchプロパティ】

searchプロパティを参照します。

ただし「location_search.html?actor=Yokohama&id=123」のようなアドレスに対して、searchプロパティの戻り値は「?actor=Yokohama&id=123」のような値となります。

このままでは扱いにくいので、個々のキー値にアクセスするためにはURLSearchParamsオブジェクトを利用すると便利です。

let params = new URLSearchParams(location.search);
console.log(params.get('actor'));
// 結果:Yokohama
console.log(params.get('nothing'));
// 結果:null

URLSearchParamsコンストラクター(let params = new URLSearchParams(location.search);)の引数には、解析対象のクエリ文字列を渡します。

冒頭で見たように、searchプロパティの先頭には、区切り文字の「?」が入っていますが、URLSearchParamsが内部的に除去します。

アプリ側で意識する必要はありません。

あとは、getメソッド(ソースコード2行目と4行目)でキー名を指定することで、対応する値を得られます。

キーが存在しない場合、getメソッドはnullを返します。

URLSearchParamsオブジェクトのポリフィル

ポリフィル(Polyfill)とは、ブラウザーの実装で不足している部分を埋めるためのライブラリです。

URLSearchParamsでもポリフィルが用意されており、たとえば上のサンプルであれば、以下のコードを.htmlファイルに追加することで、Internet Explorerでも動作するようになります。

<script src="https://cdn.jsdeliver.net/npm/url-search-params-polyfill@4.0.4/index.min.js"></script>

historyオブジェクト

historyオブジェクトとは

履歴に沿って前後のページへの移動を制御したいときは、ブラウザーのページ履歴を管理しているhistoryオブジェクトを利用します。

historyオブジェクトは、次に掲げる表のようなメソッドを用意しています。

【ページ移動のためのメソッド】
メソッド概要
back()前のページに進む
forward()次のページに進む
go(num)numページだけ移動する(負数で前に戻る)

ブラウザー履歴に従ってページを前後に移動する【back/forward/go】

以下は、テキストボックスで指定されたページ数だけ移動する例です。

<form>
  <label for="page">ページ数</label>
  <input id="page" name="page" type="number" size="4" /><br />
  <input id="btn" type="button" value="送信" />
</form>
document.getElementById('btn').addEventListener('click', function (e) {
  let page = document.getElementById('page');
  history.go(page.value);
}, false);

指定されたページ分だけ前後に移動

履歴に現在のページの状態を保存する【pushState】

JavaScriptでページを操作したとき、そのままではその時点での状態を保存することはできません。

たとえばボタンをクリックして、ページの内容を更新したあと、クリック前の状態に戻すために[戻る]ボタンを押したとしても、そのまま1つ前のページに戻ってしまうはずです。

JavaScriptアプリにおける[戻る]ボタンの挙動

そこで、JavaScriptによる操作をブラウザーの履歴として保存するのが、history.pushStateメソッドの役割です。

pushStateメソッド

history.pushState(state, title [,url])
state  状態データ
title  識別タイトル(未使用)
url    履歴にひも付けるURL

以下は、[加算]ボタンをクリックするたびに、ブラウザーの履歴を追加するサンプルです。

ボタンクリックで画面上の数字がカウントアップされていくこと、[戻る]ボタンをクリックすることでカウントダウンする(=前の状態に戻せる)ことを、それぞれ確認してください。

<input id="btn" type="button" value="加算" />
<span id="counter">0</span>回クリックされました。
let state = 0;
let coutner = document.getElementById('counter');
	
// ボタンクリックごとに値を加算
document.getElementById('btn').addEventListener('click', function (e) {
  counter.textContent = ++state;
  history.pushState(state, 'State' + state, state + '.html');
}, false);
	
// ブラウザーの[戻る]ボタンによって、その時点のカウント値を表示
window.addEventListener('popstate', function (e) {
  state = e.state;
  counter.textContent = state;
}, false);

[加算]ボタンをクリックすると、履歴が増えていく

pushStateメソッド(上のJavaScriptのコード7行目)の引数stateには、あとからその時点での状態を復元するのに必要となる情報を指定します。

ここでは、変数state(ボタンのクリック回数)を設定していますが、非同期通信などでコンテンツを取得している場合には、リクエスト時のキーとなる情報を設定することになるでしょう。

複数のキー情報がある場合には、(数値/文字列ではなく)オブジェクトを指定してもかまいません。

[戻る]ボタンを押されたときの挙動は、popStateイベントリスナーで表します。(上のJavaScriptのコード11~13行目)

先ほどのpushStateメソッドで設定された状態(引数state)には、イベントオブジェクトのstateプロパティでアクセスできます。

ここでは、取得した状態データを利用して、ページを復元しています。

navigatorオブジェクト

navigatorオブジェクトとは

JavaScriptでクライアントサイド開発を進めるうえで、その時どきのブラウザーシェアを無視することはできません。

アプリを開発/テストする際に、どこまでブラウザーをサポートするかによって、工数が大きく左右されるからです。

ブラウザーシェアについては、https://gs.statcounter.com/が参考になります。

国内のデスクトップブラウザーシェア

サポートするブラウザーが決まったところで、アプリはいずれの環境でも同じく動作するよう、対策を施す必要があります。

近年は、ずいぶん標準化が進んでいるとはえい、同じオブジェクトでも、ブラウザーによって微妙に挙動が異なる場合があるからです。

このような対策のことをクロスブラウザー対策といいます。

クロスブラウザー対策には、大きく以下の手法があります。

  • ブラウザーの種類/バージョンに応じてコードを分岐する
  • 特定の機能の有無によってコードを分岐する

これらの手法について、以下では具体的な例を見ていきましょう。

ブラウザーの種類・バージョンを確認する

navigatorオブジェクトの次に掲げる表のプロパティを参照してください。

【navigatorオブジェクトの主なプロパティ】
プロパティ概要
userAgentブラウザーのユーザーエージェント
appCodeNameブラウザーのコード名
appNameブラウザー名
appVersionブラウザーのバージョン
language使用する第1言語
languages使用する順に並んだ言語(配列)
oscpuOSの識別名
platformプラットフォーム名

この中でもよく利用するのが、ブラウザーの種類/バージョンを表すuserAgentプロパティです。

非標準のプロパティとしてappNameappCodeNameなどもありますが、こちらはブラウザーを判別できません。

次に掲げる表は、主なブラウザーでのユーザーエージェント文字列です。(ただし、利用しているプラットフォーム/バージョンによって、結果は異なる可能性があります。)

【主要なブラウザーのユーザーエージェント文字列(例)】
ブラウザーユーザーエージェント文字列
ChromeMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36
FirefoxMozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0
EdgeMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362
Internet ExplorerMozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; Touch; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; Tablet PC 2.0; rv:11.0) like Gecko
OperaMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 OPR/66.0.3515.103
SafariMozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebkit/605.1.15 (KHTML,like Gecko) Version/11.1.2 Safari/605.1.15

ブラウザーがChromeであるかどうか判定する

たとえば以下は、ユーザーエージェントを利用して、ブラウザーがChromeであるかどうかを判定する例です。

ここでは「Chromeです。」という文字列だけをログ出力していますが、実際のアプリではChrome環境でのみ実行すべきコードを記述することになるでしょう。

Chromeの判定にはユーザーエージェントに「chromeという文字列が含まれており、edge/oprが含まれていない」ことを確認します。

Edge/Operaのユーザーエージェントにも「chrome」という文字列がふくまれているので、これを除去しなければならない点に注意してください。

let agent = window.navigator.userAgent.toLowerCase();
if ((agent.indexOf('chrome') > -1) && (agent.indexOf('edge') === -1) && (agent.indexOf('opr') === -1)) {
  console.log('Chromeです。');	
}

ブラウザーがChrome/Firefox/Edge/IE/Opera/Safariのいずれであるかを判定する

以下はChormeを含めた主要ブラウザーを判定するためのサンプルコードです。

let agent = window.navigator.userAgent.toLowerCase();

if ((agent.indexOf('chrome') > -1) && (agent.indexOf('edge') === -1) && (agent.indexOf('opr') === -1)) { // Chromeの場合
  console.log('Chromeです。');	
} else if (agent.indexOf('firefox') > -1) { // Firefoxの場合
  console.log('Firefoxです。');
} else if (agent.indexOf('edge') > -1) { // Edgeの場合
  console.log('Edgeです。');
} else if (agent.indexOf('trident/7') > -1) { // IEの場合
  console.log('Internet Explorerです。'); 
} else if (agent.indexOf('opr') > -1) { // Operaの場合
  console.log('Operaです。'); 
} else if ((agent.indexOf('safari') > -1) && (agent.indexOf('chrome') === -1)) { // Safariの場合
  console.log('Safariです。'); 
}

ブラウザーが特定の機能をサポートしているかどうかを判定する【機能テスト】

機能テストと呼ばれる方法を利用します。

これは、特定の機能(プロパティ/メンバー)を利用する前に「とりあえず呼び出してみて、存在するならば、その機能を実際に利用する」という手法です。

たとえば以下の例では、ブラウザーがnavigator.geolocationをサポートしているかを判定し、提供している場合にだけ、以降の処理を実行します。

if (navigator.geolocation) {
  // ...geolocationを使ったコード...	
} else {
  console.log('Geolocationは、使えません。');	
}

一般的には、navigator.userAgentによる処理の振り分けよりも、機能テストを優先して利用してください。

userAgentによる分岐では、ブラウザーの種類/バージョンが増えるたびに見直しが必要となりますし、なにより機能の有無を判定する機能テストのほうが目的特化している分、コードの意図が明確になるからです。

userAgentによる判定は、特定のブラウザー/バージョンに依存したバグを回避するなど、限定した目的にとどめるべきです。


コメントを残す

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