javascriptからDOMを操作する

Posted コメントするカテゴリー: javascript

javascriptからDOMを操作する

これまではjavascriptを言語仕様の目線から調べたものを書いてきました。

ここからはjavascriptから画面に表示されたHTMLを変更したり、更新したりする方法を試します。

一言で画面に表示されたHTMLといっても、javascriptがどう捉えているのか、わかったうえでプログラムを書かないと制御が難しくなります。

すべてのHTMLファイルはWindowオブジェクトを持ち、Windowオブジェクトの中にdocumentプロパティがあります。
documentプロパティはDocumentオブジェクトを参照し、このDocumentオブジェクトが画面内のコンテンツを表しています。

この時、画面内のコンテンツを表すものが、DOMと呼ばれるものです。
(DOMはDocument Object Modelの略です)

フレーム間の変数の値の受け渡しについて

Posted コメントするカテゴリー: javascript

フレーム間の変数の値の受け渡しについて

フレーム間の変数の値の受け渡しについて

前回の投稿では、フレームを利用した場合の、親フレームと子フレームの関係で動きを試しました。

今回は、一つのフレームに子フレームが複数ある場合、
その子フレーム間での変数の値がどのように取得できるのかを試してみます。

前回の投稿と同じ、以下のようなHTMLを用意します。
(便宜上jquery.jsを読み込んでいますが、必須ではありません)

まず、各フレームをまとめるtest.htmlを一つ

<html>
<head>
<title>フレーム使用例</title>
<script src="./jquery.js"></script>
<script src="./main.js"></script>
</head>

<frameset rows="150,*">
    <frame id="frm1" name="frm1" src="test1.html">
    <frameset cols="250,*">
        <frame id="frm2" name="frm2" src="test2.html">
        <frame id="frm3" name="frm3" src="test3.html">
    </frameset>
    <noframes>
        このページはフレームを使用しています。
    </noframes>
</frameset>

</html> 

それと、

各フレームのhtmlファイル

test1.html
test2.html
test3.html

test1.html、test2.html、test3.html
はそれぞれ親フレームから呼ばれる子画面のHTMLです。

それぞれのhtmlファイルは以下のように書きました。わかりやすいように内容はほぼ同一で、画面表示時にアラート出力をしています。(最終的な検証では削除します)

test1.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>test window1</title>
    <script src="./jquery.js"></script>
</head>
<body>

<script type="text/javascript">
alert("クライアントサイドjs 1");
var test1 = "aaa";
alert(window.test1);
</script>

test 1

</body>
</html>

test2.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>test window2</title>
    <script src="./jquery.js"></script>
</head>
<body>

<script type="text/javascript">
alert("クライアントサイドjs 2");

//ここでtest1.html内の変数にアクセスしている
alert(parent.frm1.test1);

</script>

test 2

</body>
</html>

test3.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>test window3</title>
    <script src="./jquery.js"></script>
</head>
<body>

<script type="text/javascript">
alert("クライアントサイドjs 3");
</script>

test 3

</body>
</html>

ここまでのファイルを準備して、各ファイルにjavascriptを埋め込んで動作を確かめてみます。

まず、test1.htmlのjavascript内に、以下のように変数を代入します。

let test1 = "aaa";

次に、このtest1.htmlで代入した変数を、test2.html内で読み込んでアラート表示してみます。

test2.html

alert(parent.frm1.test1);

ここまでで、ブラウザからtest.htmlにアクセスすると、
ダイアログがtest1.htmlの読み込みで2回、test2.htmlの読み込みで2回、test3.htmlの読み込みで1回、表示されます。

ポイントとなる点は、test1.htmlで格納した変数に、test2.htmlからアクセスしている点です。
test2.htmlから

parent.frm1.test1

という書き方をして、フレームごしのtest1.htmlに代入した変数にアクセスしています。

ところが、最近のjavascriptの変数の宣言はletで書くことが主流といえるので、
試しにletで書いてみたところ、test2.htmlのダイアログ出力ではundefinedが表示されました。
これはletで宣言した変数にフレーム間で呼び出すことができないスコープが存在していることと言えます。

実際にサーバ上に設置したhtmlは以下のURLで確認できます。
https://propansystem.net/blogsample/js/015/

フレームをまたぐ関数の受け渡しについて

次に、フレーム間で変数を受け渡すのではなく、関数を宣言して受け渡してみます。

先ほどと同様に、test1.html、test2.html、test3.htmlを用意し、
test1.html内に関数を宣言します。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>test window1</title>
    <script src="./jquery.js"></script>
</head>
<body>

<script type="text/javascript">

function test1func() {
	alert("test1func");
}

</script>

test 1

</body>
</html>

次に、test2.htmlで、test1.htmlで定義した関数を呼び出します。

test2.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>test window2</title>
    <script src="./jquery.js"></script>
</head>
<body>

<script type="text/javascript">
alert("test2 start !");
parent.frm1.test1func();
</script>


test 2

</body>
</html>

こうすることで、test2.htmlからフレームごしのtest1.htmlに書かれた関数「test1func」を呼ぶことができます。

この時に注意する点としては、
・関数をフレームごしに呼ぶ場合は、スコープに注意する。
・関数内でグローバル変数を参照すると、「関数を書いた側」のフレームのプロパティが検索される。
・ユーザ定義関数とは別に、javascriptがもともと定義されている関数を呼び出す場合は、コンストラクタとコンストラクタのプロトタイプオブジェクトは、フレームごとに別々に持っている。

という点になります。
ただ、ここは詳しく実装して検証してみないとイメージしずらいので、
別な投稿としてとりあげたいと思います。

フレームオブジェクトについて

Posted コメントするカテゴリー: javascript

フレームについて

ウィンドウをオープンした時にwindowオブジェクトをjavascriptから参照したように、
同じドキュメント内にあるframeに対しても、親画面から子画面(子frame)に対して参照ができます。

例えば、以下のようなHTMLを用意します。

test1.html
test2.html
test3.html

はそれぞれ、親画面から呼び出される子画面(フレーム)です。
親画面から上記の3ファイルを呼び出すHTMLを用意します。

<html>
<head>
<title>フレーム使用例</title>
<script src="./jquery.js"></script>
<script src="./main.js"></script>
</head>

<frameset rows="150,*">
    <frame id="frm1" src="test1.html" name="ue">
    <frameset cols="250,*">
        <frame id="frm2" src="test2.html" name="hidari">
        <frame id="frm3" src="test3.html" name="migi">
    </frameset>
    <noframes>
        このページはフレームを使用しています。
    </noframes>
</frameset>

</html> 

画面表示は、このようになります。

このHTMLに対して、main.js内で以下のようにframeオブジェクトを参照してみます。

$(function(){

	//フレームを取得(オブジェクトとして取得される)
	let frm1 = $('#frm1');
	console.log("frm1 -> " + frm1);

	//フレームのwindowオブジェクトを参照する方法
	let frm1_cw = frm1[0].contentWindow;
	console.log("frm1_cw -> " + frm1_cw);
	
	//windowオブジェクトに標準に定義されている「frames」プロパティを参照する
	console.log("frames -> " + frames[0]);

});

ブラウザにアクセスした結果、コンソール画面には次のように出力されます。

frm1 -> [object Object]
frm1_cw -> [object Window]
frames -> [object Window]

出力結果から、「$(‘#frm1’)」で参照した場合と、「frm1[0].contentWindow」と「frames[0]」で参照したものはオブジェクトの種類が異なります。

id指定で取得したものはObjectになり、残りの2つで参照したものはwindow(オブジェクト)になります。

フレーム内のdocumentに対して、操作をしてみます。

先ほどのjavascriptに以下のように追記します。

	window.onload = function(){
		frames[1].document.body.style.background = "#99CCFF";
	}

ここでは、2つめのフレームに対して、bodyタグの背景色を変更しています。
window.onloadを使わない場合、親ウィンドウの全てのコンテンツが読み込まれないままjavascriptの処理が終わるので、背景色をjavascriptで変更した後、子ウィンドウがロードされて処理が無効になってしまいます。

実行した画面は次のようになります。

実際に設置した動作例は、以下のURLです。
https://propansystem.net/blogsample/js/014/

ウィンドウクローズ

Posted コメントするカテゴリー: javascript

ウィンドウのクローズ

前回の投稿ではウィンドウをオープンを試しました。
今回は開いたウィンドウを閉じてみます。

まずは親ウィンドウとなるHTMLを用意します。
jqueryと、main.jsを読み込んでいます。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>test</title>
    <script src="./jquery.js"></script>
    <script src="./main.js"></script>
</head>
<body>

親ウィンドウ

</body>
</html>

次に、main.jsの中身です。

$(function(){

    //新規ウィンドウを開く
    let w = window.open("test.html", "test", "width=400,height=500");

});

スクリプトがロードされたら問答無用で新しいウィンドウ(test.html)を開いています。

test.htmlの中身は次のように書いておきます。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>test window</title>
    <script src="./jquery.js"></script>
    <script src="./sub.js"></script>
</head>
<body>

test window

<div id="cwindow">ウィンドウを閉じる</div>

</body>
</html>

最後にsub.jsを用意します。

$(function(){
	$("#cwindow").click(function(){
		window.close();
	});
});

これも、シンプルに「

ウィンドウを閉じる

」のdivタグがクリックされた時にウィンドウを閉じる命令を書いています。

ここまでの一連のファイルで、親ウィンドウから子ウィンドウを開いて、子ウィンドウのリンクをクリックすると、子ウィンドウが閉じる。
という動きになります。

ウィンドウのクローズ時の注意点

ここで、注意が必要なのは、子ウィンドウを閉じる時の命令です。
上記の例では、windowオブジェクトを直接使っていますが、
親ウィンドウで生成した

let w = window.open("test.html", "test", "width=400,height=500");

の「w」を使って、

w.close();

という命令もありますが、この例ではそれは動作しません。

動作しない理由としては、親ウィンドウで定義した「w」に子ウィンドウからアクセスできない為に子ウィンドウを閉じることができません。

ウィンドウオープン

Posted コメントするカテゴリー: javascript

ウィンドウオープン

javascriptを使って、開いているウィンドウ(ブラウザ)からさらに別なウィンドウを開く方法があります。

通常、ブラウザで開いている画面とは別に、別なブラウザで別画面を開いている場合は、その2つのウィンドウ間ではjavascriptのプログラムからは直接アクセスできません。

ただ、今回試すウィンドウオープンの方法で開いたウィンドウは、javascriptを使ってDOM要素にアクセスしたり、値の受け渡しが可能になります。

最も簡単なウィンドウを開く命令を書いてみます。

//新規ウィンドウを開く
let w = window.open("test.html", "test", "width=400,height=500");

実行すると、ブラウザにアクセスした際にjavascriptが起動されると、新規ウィンドウが開きます。

//ウィンドウオブジェクトを生成し、アラートを開く
let w = window.open();
w.alert("test");

//ウィンドウオブジェクトを生成し、新規ウィンドウを開き、指定URLへ遷移する
let w2 = window.open();
w2.location = "https://google.com";

上記の記述方法は、一見すると最初に書いたサンプルと同じ動きになりますが、
openerプロパティを使うことで、開いたウィンドウのオブジェクトを取得できる点が大きく違います。
具体的な動きを試しに書いてみます。

//ウィンドウオブジェクトを生成し、新規ウィンドウを開き、指定URLへ遷移する
let w3 = window.open();
console.log(w3.open().opener);


let w4 = window.open();
w4.location = "https://google.com";
console.log(w4.open().opener);

w3の場合と、w4の場合は、console.logの出力結果が違い、どちらもオープンしたウィンドウの情報が取得できるのですが、
locationの値を代入したw4の場合は、取得結果がw3とは異なる結果になっていることがわかります。

下記のキャプチャ画像は、それぞれのログ出力結果です。

w3のログ出力

w4のログ出力

windowsオブジェクトとID

Posted コメントするカテゴリー: javascript

windowsオブジェクトとID

windowドキュメント内でのID属性の使い方について、まとめます。

javascriptライブラリのjqueryでの開発が主流だった時には、ひとつの画面内の(ドキュメント内の)DOM要素に対してID属性を指定して、要素にアクセスするという方法が多くありました。
もちろんjqueryを使わない、javascriptのみでID属性を使ってDOMにアクセスする方法もあり、その場合に注意する点をまとめてみます。

・DOMに対し、ID属性を付与する場合、windowオブジェクトに同じ名前のプロパティが存在する場合、処理が意図しないものになる
・上記の場合、同一のID属性を定義したDOM要素がある場合は、ドキュメント内のDOM要素が定義される。(例えば、inputや)
・IDの名称がHTMLのタグ名と同じものは指定できない(指定したとしてもjs上で一意ではないので誤動作の原因になる)

ダイアログボックス

Posted コメントするカテゴリー: javascript

ダイアログボックス

ダイアログボックスを試してみます。
ダイアログボックスは画面表示時に、ユーザの操作に関係なく、メッセージを「表示/確認/入力」する為のダイアログを表示します。

具体的には、alert、confirm、propmtがあり、それぞれブラウザの表示またはロード中の状態を停止した上で、画面描画系の操作を受け付けない形で表示されます。

主に、alertはプレーンテキストによるメッセージ、confirmはOK/キャンセルの確認ボタン、promptはユーザの入力待ちのダイアログボックス、をそれぞれ表示します。
下記にシンプルな例を書いて試してみます。

alert('入力お願いします');

do {
    name = prompt("お名前を入力してください");
    conf = confirm(name + 'でよろしいですか?');
} while (!conf || (name == '' || name == 'null'));

alert('入力ありがとう' + name + 'さん');

ブラウザで実行すると、まず最初に「入力をお願いします」というメッセージダイアログが表示されます。
ダイアログ上の「OKボタン」を押すと次に、「お名前を入力してください」というメッセージとともに、テキスト入力欄があるダイアログボックスが表示されます。
その次に「(入力したお名前)でよろしいですか?」という確認ダイアログが表示されます。
確認ダイアログでOKボタンを押した場合は、最後に「入力ありがとう(入力したお名前)さん」のメッセージが表示されます。

サンプルなので、実用性はほぼ無く入力チェック等は甘いですが、必要最小限の動作の確認はできました。

最もシンプルなダイアログボックスの表示方法ですが、最近のjavascript界隈の動きでは、reactやvue、その他ライブラリを使ったダイアログボックス、ダイアログウィンドウ、モーダルウィンドウ、等の表示方法、利用方法が多数あります。
利用シーンは開発環境、開発体制等に応じて実装方法を合わせていくようにすると良いかと思います。

Screenオブジェクト

Posted コメントするカテゴリー: javascript

Screenオブジェクト

Screenオブジェクトを試してみます。
ScreenオブジェクトはWindowオブジェクトの画面に関係する情報を提供します。

下記にサンプルコードを書いてみます。

let scr1 = screen.availWidth;
console.log('scr1 -> ' + scr1);

let scr2 = screen.availHeight;
console.log('scr2 -> ' + scr2);

let scr3 = screen.colorDepth;
console.log('scr3 -> ' + scr3);

let scr4 = screen.pixelDepth;
console.log('scr4 -> ' + scr4);

上記を実行すると、下記のような結果になります。

scr1 -> 1920
scr2 -> 1170
scr3 -> 24
scr4 -> 24

検証に使用したPCでは、有効画面幅1920px、有効画面高さ1170px、画面の色深度24、仮想画面の色深度24という値が出力されました。
この値は実行するPCやブラウザにより影響を受けるので、それぞれのマシンでの値が出力されます。

また、有効画面の幅と高さではなく、物理的なモニターの幅と高さを求めるには、次の出力命令を使います。

let scr5 = screen.width;
console.log('scr5 -> ' + scr5);

let scr6 = screen.height;
console.log('scr6 -> ' + scr6);

出力結果は

scr5 -> 1920
scr6 -> 1200

となり、実際のモニターのサイズ(WUXGA)の値が出力されます。(出力結果は実行するPC環境によって大きく異なります)

Navigatorオブジェクト

Posted コメントするカテゴリー: javascript

Navigatorオブジェクト

Navigatorオブジェクトを試してみます。
Navigatorオブジェクトはブラウザ関係の情報が格納されています。

下記にサンプルコードを書いてみます。

let navi1 = navigator.userAgent;
console.log('navi1 -> ' + navi1);

let navi2 = navigator.appName;
console.log('navi2 -> ' + navi2);

let navi3 = navigator.appVersion;
console.log('navi3 -> ' + navi3);

let navi4 = navigator.platform;
console.log('navi4 -> ' + navi4);

let navi5 = navigator.oscpu;
console.log('navi5 -> ' + navi5);

上記を実行すると、下記のような結果になります。

navi1 -> Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0
navi2 -> Netscape
navi3 -> 5.0 (Windows)
navi4 -> Win32
navi5 -> Windows NT 10.0; Win64; x64

検証に使用したブラウザはFirefoxで、同じコードをChromeで確認すると、次のようになります。

navi1 -> Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36
navi2 -> Netscape
navi3 -> 5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36
navi4 -> Win32
navi5 -> undefined

ただ、値の4つめ「navi4」のplatformについては、検証に使っているOSが64bit版windows10ですが、なぜかWin32という出力になります。
これはブラウザ間の出力を統一する為に、先にChromeが「Win32」という出力をする作りにした為、その後にFirefoxがChromeの出力に合わせ、ブラウザが返すOS情報を統一して出力するようになったという歴史的背景があるようです。
こちらのサイト「Firefox サイト互換性情報」にその辺りの説明が詳しく記述されています。

また、5つめの「navi5」についてはChromeでは定義されておらず、undefinedになります。
この辺りもブラウザ間で微妙に差異がある為、プログラム実装時には注意が必要と言えます。

locationオブジェクト

Posted コメントするカテゴリー: javascript

locationオブジェクト

locationオブジェクトを試してみます。
locationオブジェクトはウィンドウに表示されているURLを表したり、その他ウィンドウのURLに関連する操作を行えます。
このオブジェクトに対して、メソッドやプロパティを試してみます。

簡単にURL関連のlocationオブジェクトを表示してみます。

console.log(window.location.href);
console.log(location.href);
console.log(Location.href);

上の例では、わざと間違った記述をしてみました。
結果は、

https://javascriptを実行したドメイン名/
https://javascriptを実行したドメイン名/
undefined

という結果になりました。
3つめの命令では、locationの先頭の文字が大文字の為、オブジェクトとして解釈できなかった為、undefinedになりました。

また、1つめと2つめの違いは、「window.」を記述するかしないか。という違いでしたが、
これはlocationオブジェクト自体windowオブジェクトなので、省略しても2つめの記述が問題なく動作することがわかります。

それでは、別な例を試してみます。

console.log(location.host);
console.log(location.hostname);
console.log(location.protocol);
console.log(location.port);
console.log(location.pathname);
console.log(location.search);
console.log(location.hash);

それぞれ実行した結果は

propansystem.net
propansystem.net
https: main.js
<empty string> main.js
/実行したjavascriptまでのパス/ main.js
<empty string> main.js
<empty string>

という結果になりました。

ここでは詳しい言語仕様は割愛しますが、上記のサンプルを動作した際のURLに対し、
もしURLの末尾にパラメータが「index.php?aaa=1&bbb=2&ccc=3」のように付与されている場合は、
location.searchの結果が

?aaa=1&bbb=2&ccc=3

という出力になります。
javascriptでは、URLのパラメータを利用してはいけないというルールはないので、
開発プログラムによっては、サーバサイドプログラムのGETの値と同様の実装の仕方ができると言えます。
(例えばPHP言語だと「$_GET」パラメータのように)

windowオブジェクト

Posted コメントするカテゴリー: javascript

Windowオブジェクト

windowオブジェクトについて、簡単なサンプルを書きつつ、挙動をみてみます。

タイマー

以前の投稿で、windowオブジェクトの指定時間経過後のプログラム実行のサンプルを書きました。

その動きもタイマーの機能と言えますが、指定時間経過後ではなく、
指定時間ごとの一定の間隔でプログラムを実行するという命令もあります。
簡単に下記のサンプルを書いて動かしてみます。

let i = 0;
setInterval("output_log(i++)", 3000);

function output_log(i) {
    console.log('hello -> ' + i);
}

setIntervalを使ったシンプルな例です。
第一引数については、主にメソッドを指定しますが、ダブルクォートで囲わないと動作しませんでした。

同一出身ポリシー(同一オリジンポリシー)について

Posted コメントするカテゴリー: javascript

同一出身ポリシー(同一オリジンポリシー)について

javascriptで読み込まれたプログラムが、1つのサーバ(ドメイン)から読み込まれた場合、
特に問題なく動作しますが、2つのサーバA、Bがあり、サーバAにアクセスした際に、サーバBのjavascriptを読み込む実装方法も実現できます。

この時に同一出身ポリシーの考え方と挙動が適用されるので理解と注意が必要です。

同一出身ポリシーについて簡単にまとめます。

・①サーバAから読み込まれたjavascriptはサーバBのドキュメントにアクセスすることはできない。
・②同じポリシーのウィンドウや、ドキュメントにはアクセスができる。
・③プロトコル(httpやhttps)が異なる形でドキュメントがロードされた場合、別な出身ポリシーとなる。
・④javascriptのプログラムファイルは、別サーバからロードしても、動作に問題はでない。
(サーバAにあるjavascriptプログラムを、サーバBで読み込んだ場合、サーバBにあるドキュメントにアクセスができる)
・⑤AJAX等の通信時にも同一出身ポリシーが適用され、サーバAからAJAX通信をした場合、サーバBにアクセスすることはできない。(注…javascriptライブラリの実装方法で別サーバ間での通信を行う技術的解決方法はある)
・⑥サブドメインを利用したサーバ間通信では、別なサーバとみなされる。(javascriptのDocumentオブジェクトの設定方法により、同一出身ポリシーとみなし、それぞれ別なサブドメインにアクセルする方法はある)

細かい部分では実際に開発を進めていく上で挙動を確認しながら把握することがよいと思います。

④については、AJAX通信を利用し、jsonp形式のコールバック関数経由で別サーバへの通信を行うことができます。
上記はjqueryライブラリを利用することで実装ができます。

また、最近ではjqueryライブラリ以外のjavascriptフレームワークが主流になってきているので、それぞれのフレームワークのドキュメントをもとに開発するケースが多いです。

javascriptの制限

Posted コメントするカテゴリー: javascript

javascriptの制限

javascriptをwebブラウザで動作させる場合、各ブラウザにより差がありますが、機能が制限されています。
機能の制限については、ブラウザごとに差があるので、プログラムの開発中に都度確認する必要があります。

主な制限については、ブラウザごとの差異はなく、一般的に下記の点があります。

・クライアント側のファイルにアクセスすること
・クライアント側の任意のディレクトリにアクセスすること
・汎用的なネットワークプログラムができないこと(APIの利用やHTTPプロトコル通信を利用する方法はアリ)
・javascriptから開いた新規ブラウザウィンドウの制御に対しての制限
・ファイルアップロード処理のイベント処理(勝手にクライアントマシンのファイルをアップロードすることができないように)
・他サーバからロードされたjavascriptプログラムを、クライアントマシンのjavascript側でロードし実行すること

また各種ブラウザごとの制限については、ここでは網羅できませんが、
システムの開発中に機能ごとに調べて実装を進める必要があります。

クライアントサイドのjavascriptの実行順について

Posted コメントするカテゴリー: javascript

クライアントサイドのjavascriptの実行順について

javascriptがブラウザ上で実行される実行順についてまとめます。

全てのブラウザ、全ての場合に下記のような実行順が保証される訳ではなく、
ブラウザがHTMLやjavascript、その他、ブラウザ上に読み込まれるコンテンツをローディング、解釈、表示(出力)などを行う場合の参考の実行順として考えることができます。

また、HTMLパーサもブラウザごとのバージョンにより挙動が細かく変わるので、一概に実行環境を固定して考えることは、現実的ではないと考えられます。

さらにjavascriptのフレームワーク固有の挙動の違いもあるので、それぞれの実行順はプログラムを書いて確かめてみることがベストと思います。

  • 1.ブラウザの起動と同時に、Documentオブジェクトが作られる。
    HTML要素、テキスト、の解釈
    ドキュメントに対してElement、Textを追加。
  • 2.インラインスクリプト(HTMLタグ内に記述したjavascript)や、外部javascriptスクリプト(の読み込み指定がある場合)を実行する。
    この時の実行は同期実行される。
  • 3.HTMLパーサはasync属性が設定された<script>要素を解釈するときは、スクリプトのダウンロードを開始、ドキュメントの解釈も開始、
    このとき、HTMLパーサはダウンロードや、解釈を待たない。
  • 4.defer属性が設定されたスクリプトがドキュメント中に記述された順で実行される。
    非同期で実行されるスクリプトもある。
  • 5.ドキュメントが解釈できたら、Documentオブジェクトに対して、DOMContentLoadedイベントが発生。
    プログラム実行は非同期イベントドリブンの状態になる。
  • 6.ブラウザが全て読み込み完了した後、Windwosオブジェクトに対してloadイベントを発生させる。
    この時、サイズの大きい画像や、その他のファイルが引き続きローディング中になる場合もある。
  • 7.上記までの処理が完了した後、ユーザの入力や操作によるイベント、時間経過等によるイベントハンドラが非同期で呼び出される。

HTML内でのjavascriptコードの記述

Posted コメントするカテゴリー: javascript

HTML内でのjavascriptコードの記述

javascriptをHTML文書内で使用する方法についてまとめます。

HTML内にjavascriptを書く方法は、次の書き方があります。

1.scriptタグ内に記述する

2.外部ファイル化したjavascriptを読み込む

3.HTMLタグにイベントハンドラとして記述する

次に、それぞれ具体的にどのように書くのかを確かめつつ、書いてみます。

1.scriptタグ内に記述する

headタグもしくはbodyタグ内にscriptタグを書いてその中に記述する方法です。
次のようなHTMLファイルを作成し、ブラウザからアクセスしてみます。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test</title>

<script>
alert('test');
</script>

</head>
<body>

</body>
</html>

アクセスした結果、

<script></script>

タグ内に記述したjavascriptが動作し、アラートが表示されます。

2.外部ファイル化したjavascriptを読み込む

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test</title>

<script src="./main.js"></script>

</head>
<body>

</body>
</html>

外部ファイルのjavascriptには、上記同様に次のように書きます。
ファイル名はmain.jsとして作成し、HTMLファイルと同じ場所に置きます。

alert('test2');

HTMLファイルにアクセスすると、ダイアログが表示されます。

src属性を書いた場合は、

<script></script>

タグ内に記述したjavascriptは無効になります。

外部ファイル化した場合、javascriptのプログラムファイルがブラウザにキャッシュされるので注意が必要です。(注:ブラウザ標準の動作として)

src属性には外部サーバのjavascriptを読み込むことも可能です。

3.HTMLタグ内にインラインでjavascriptを記述する

HTMLタグ属性にjavascriptのプログラムを直接記述することができます。

下記の簡単なサンプルを書いて、ブラウザでアクセスしてみます。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test</title>

</head>
<body>

<div class="test_div" onClick="alert('test3');">testABC</div>

</body>
</html>

上記のサンプルでは、ブラウザにアクセスした段階では、特になにも起きません。

画面上に「testABC」という文字列が表示されるので、その文字列をクリックしたタイミングでアラートが表示されます。

この場合の、onClickはイベントハンドラと呼ばれており、クリックした場合の処理を走らせることができます。
この他にもイベントハンドラには種類があるので、目的に合わせてプログラムを実装します。

また、alertメソッドのみでサンプルを書きましたが、実際にはここに外部ファイル等に定義したユーザ関数を割り当て、
その関数内でロジックを実装していく形が多いです。

実際のプロダクト開発では、HTMLに直接記述する場面は多くはなく、外部ファイル化したjavascriptを利用する、
または最近ではjavascriptのフレームワークが多数あるので、その機構に沿ってロジックを組み立てる場合が多いです。

例としては、React、Vue、Next、Angular等々…。多数のフレームワークが存在します。
このブログでもjavascriptの基本的な部分を抑えた後、各種フレームワークに触れていきます。

windowオブジェクトについて

Posted コメントするカテゴリー: javascript

windowオブジェクトについて

クライアントサイドでのjavascriptでは、windowオブジェクトが一番最初のスコープチェーンと考えることができます。
windowオブジェクトのプロパティやメソッドは、グローバル変数、グローバル関数としてみることができます。

例えば次のような非常にシンプルなHTMLを作成し、main.js内にwindowオブジェクトのlocationプロパティに値を設定する。
というファイルを用意して、ブラウザからhtmlファイルにアクセスすると、locationに設定したURLを読み込む動きになります。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test</title>

<script src="./main.js"></script>

</head>
<body>

test123

</body>
</html>
//指定URLへ遷移する
window.location = "https://google.com";

上記の例は、windowオブジェクトに対してのプロパティを操作しましたが、
メソッドも用意されています。

例えば、次のように書きます。

window.setTimeout(function() {
	console.log("set!");
}, 5000);

この場合は5秒後にconsoleログ出力が一度だけされます。

setTimeout命令(メソッド)により、引数に定義した無名関数が、第二引数の5000ミリ秒後に実行される。という仕組みです。

この時、「window.」と明示的に指定していますが、setTimeoutのみを記述しても動作します。
windowオブジェクトはグローバルオブジェクトなので、windowオブジェクトが持つメソッドもグローバルメソッド(グローバル関数)として使うことができます。

また、ブラウザ上で実行するjavascriptでは、下記のプログラム要素があります。

  • windowオブジェクト(上記以外の使い方)
  • style(css)の制御
  • DOMの制御
  • HTML5APIの使用
  • グラフィックの描画(canvas)
  • サウンドの使用(再生等)

上記は一部の要素ですが、このブログ内で一つ一つ動かしながら確かめていく予定です。

クライアントサイドjavascript

Posted コメントするカテゴリー: javascript

概要

これまではjavascriptの基本的な文法を中心に、サンプルコードを書いて試してきましたが、
ここからは少し実践的なjavascriptの用途について試していこうと思います。

ご存じの通り、javascriptの言語仕様の策定やブラウザのjavascriptエンジンのバージョンアップ、各種javascriptフレームワーク、そして書き方のスタイルの変化、等が早いスピードで進化しています。

実際にそれら全ての情報を収集し、常に最新な状態を理解することは非常に難しいですが、ポイントを押さえつつある程度の幅を持たせて実践的な書き方を試そうと思います。

try catchの複数の条件判定について

Posted コメントするカテゴリー: javascript

try catch の複数の条件判定について

これまでの投稿でtry catch 文については、深く掘り下げていませんたが、
try catchの基本的な書き方は以下のようになります。

try {
  //処理
} catch(e) {
  //エラー時の処理
} finally {
  //最終的に必ず実行する処理
}

まず、tryで通常の処理を行い、この中で何らかのエラーが発生した場合や、throw文によって例外が投げられると、catchブロックに処理が移ります。

この時、catch文を複数書くことができます。

try {
  //処理
} catch(e) {
  //エラー時の処理1
} catch(e) {
  //エラー時の処理2
} catch(e) {
  //エラー時の処理3
} finally {
  //最終的に必ず実行する処理
}

このように複数書く場合は、catch(e)の引数eについて、条件判定する書き方があります。

具体的には次のように書きます。

//書き方の例1
catch(e if e instanceof TypeError)

//書き方の例2
catch(e if e instanceof RangeError)

//書き方の例3
catch(e if e instanceof EvalError)

上記は3通りのcatch文の引数の判定方法を書きましたが、現状のjavascriptプログラミングでは複数のcatch文は推奨はされていませんし、ブラウザによっては正常に動作しない場合があるので、注意が必要です。

また、上記の

TypeError
RangeError
EvalError

等は、javascriptの標準ビルトインオブジェクトであり、その中の基本オブジェクトとしてあらかじめ定義されているものです。
(詳しくは、MDN web docsに記載されています)

上記以外には、上記の3通りのオブジェクトも含め、下記のようなものが定義されています。

EvalError
InternalError
RangeError
ReferenceError
SyntaxError
TypeError
URIError

また、例のコードには書きませんでしたが、下記のような反映も書くことができます。

//tryブロックの中で、throw new Error("err1"); のように例外が投げられた場合
catch(e if e === "err1")

//throwされた例外が数値だった場合
catch(e if typeof e === "number")

いずれもcatchについての書き方になりますが、あまり複数のcatchを前提で開発を進めないほうが良いかもしれません。
またtry catch句について調べたら当記事を更新しようと思います。

ジェネレータを試してみます

Posted コメントするカテゴリー: javascript

概要

前回に投稿したジェネレータについて、実際にコードを書いて試してみます。
非常に簡単な例を書いてみます。

function* g_test(s)
{
	console.log(s);
	yield;
	
	console.log(s+1);
	yield;
	
	console.log(s+2);
}

//ジェネレータ関数に引数を渡して呼び出す
let gene = g_test(100);

//1回目の実行
gene.next();

//2回目の実行
gene.next();

//3回目の実行
gene.next();

上記を実行すると、次のようになります。

100
101
102

geneを生成した後、gene.next();とすることで、g_test関数を実行しています。
ここで、注意が必要な点は、functionの後に「*」をつけていることです。

function* g_test(s)

と記述することで、g_test()はジェネレータとしての振る舞いをする関数として定義されています。

また、ジェネレータから値を取り出す際には、関数内部で値を返すように書く方法があります。

次のようなサンプルを書いて、試してみます。

function* g_test(s)
{
	yield s;
	yield s+10;
	yield s+20;
}

//ジェネレータ関数に引数を渡して呼び出す
let gene = g_test(100);

//1回目の実行
console.log(gene.next());

//2回目の実行
console.log(gene.next());

//3回目の実行
console.log(gene.next());

上記の場合は、firefoxの開発ツールで確認したところ、下記のような出力がされます。

Object { value: 100, done: false }
Object { value: 110, done: false }
Object { value: 120, done: false }

注意する点は、ジェネレータの2回目の呼び出しと、3回目の呼び出し時に、
関数内で「yield 」の値を返しているという点です。

関数内でyieldを返すことにより、呼び出し元でObjectとして処理結果を受け取り、その内容を使用することが可能になります。

また、ジェネレータに対して、値を渡すことも可能なので、それはまた改めて試して投稿する予定です。

ジェネレータ

Posted コメントするカテゴリー: javascript

概要

javascript1.7(時期としては2006年以降)からジェネレータが実装されました。
javascriptの改定の歴史からすると、古い時期に実装されている機能ですので、お浚いを兼ねて試してみます。
ちなみに他の言語でもジェネレータという考え方があり、同様にyieldキーワードを利用する場面があります。

特徴を下記にまとめます
・ジェネレータはyieldキーワードを関数中で使用します。
・yieldは、関数から値を返します。
・呼び出し元に値を返した後も内部状態を保持します。
・ジェネレータ使用時は、yidldキーワードの実行有無に関わらずジェネレータ関数として振る舞います。
・functionキーワードを使って宣言します。

具体的なサンプルは次の投稿で試してみようと思います。

イテレータ(続き)

Posted コメントするカテゴリー: javascript

概要

前回投稿した反復処理のイテレータについて引き続き試してみます。
イテレータはコレクションに対してループ処理をする際、次の値が存在しない場合はStopIterationがスローされます。

前回試したイテレータのサンプルを少し改造して、次のようなコードを書きました。

function test2(start, end) {
	let sv = start;
	return {
		next: function() {
			if (sv > end) {
				throw StopIteration;
			} else {
				return sv++;
			}
			
		}
	};
}

//イテレータに対し引数を2つ設定し定義
let testval2 = test2(100, 105);

//定義したイテレータ内のnextを呼び出す
try {
	console.log(testval2.next());
} catch(e) {
	console.log(e);
}

try {
	console.log(testval2.next());
} catch(e) {
	console.log(e);
}

try {
	console.log(testval2.next());
} catch(e) {
	console.log(e);
}

try {
	console.log(testval2.next());
} catch(e) {
	console.log(e);
}


イテレータ内のnext()で、test2の第一引数と第二引数で開始値と終了値を指定し、
イテレータが呼ばれる回数が終了値(=endの値)を超えた時に例外「StopIteration」をスローしています。

ループ処理でtry catchを呼ぶ形のほうがコードの量が少なく綺麗になりますが、
※・・・上記のサンプルは挙動を確かめる(エラー発生行数)為にループはしていません。

注意点

イテレータ側で配列を扱う場合は、終わりの値が正しく設定されている場合は問題はないですが、
無限サイズの配列の場合は、際限なく処理されてしまいます。
実行時のメモリ消費に影響する為、startとendを決める仕様のほうが安全と言えます。

また、言語仕様として存在はしますが、使用については非推奨となっているので、注意が必要です。

反復制御について

Posted コメントするカテゴリー: javascript

概要

javascriptで反復制御をする場合の書き方をおさらいします。

for in 文はループ中にキーを出力します。

//for in ループ
let tmp = {a: 10, b: 20, c: 30}

//for in の場合
for(let v in tmp) console.log(v);

出力結果は

a
b
c

になります。

イテレータは、for in を拡張したものになります。
反復制御の際には、ある値のコレクションを対象とし、nextメソッドを使い、要素を巡回してアクセスします。
簡単な例は次のようになります。

//イテレータ
function test(strparam) {
	let V = strparam;
	return {
		next: function() {
			return V++;
		}
	};
}

let testval = test(100);
console.log(testval.next());
console.log(testval.next());
console.log(testval.next());

出力結果は

100
101
102

となります。

その他の反復制御については、別途掘り下げて投稿します。

分割代入

Posted コメントするカテゴリー: javascript

概要

分割代入について試してみます。
分割代入は、配列やオブジェクトを、1つまたは複数の変数に一度に代入します。
具体的には次のように書きます。

let [a, b, c] = [10, 20, 30];

console.log(a);
console.log(b);
console.log(c);

出力結果は

10
20
30

となります。

分割代入は、配列形式等の値を一度に代入が可能なので、等号記号「=」の左辺に配列を指定すると効果的です。

上記の例に少し手を加えてみます。

let [a, b, c] = [10, 20, 30];

//配列を分割代入を利用して、順序を変更して代入する
[a, b, c] = [b, a, c];

console.log(a);
console.log(b);
console.log(c);

この場合、出力結果は

20
10
30

になります。
見ての通りシンプルな動きになります。

次に、結果を配列で返す形にして、関数の戻り値を分割代入するサンプルを書いてみます。

function test1(x, y) {

	//引数を加算して配列で返す
	x = x + 100;
	y = y + 200;

	return [x, y];
}

let [d, e] = test1(10, 20)

console.log(d);
console.log(e);

出力結果は

110
220

になります。
配列で分割代入されていることがわかります。

代入する値の数が異なる場合

上記のサンプルでは、等号記号の左辺と右辺は同じ数でした。
では、代入する要素数が異なる場合はどのような挙動になるのか、試してみます。

//右辺が左辺よりも少ない場合
let [g, h, i] = [10, 20];

console.log(g);
console.log(h);
console.log(i);

//右辺が左辺よりも多い場合
let [j, k, l] = [30, 40, 50, 60];

console.log(j);
console.log(k);
console.log(l);

この場合の出力は、次のようになります。

10
20
undefined
30
40
50

左辺の変数iには、それに対する右辺の値が存在しない為、undefinedになります。

配列をネストしている場合

分割代入する配列がネストされている場合について試してみます。

let [m, n, o, [p, q], r] = [100, 101, 102, [201, 202], 103];

console.log(m);
console.log(n);
console.log(o);
console.log(p);
console.log(q);
console.log(r);

出力結果は

100
101
102
201
202
103

となります。

let キーワードについて

Posted コメントするカテゴリー: javascript

概要

これまでにも何度も出てきたletキーワードについておさらいをしてみます。
letは次のような画面で使用します。
・変数宣言
・ループ内でのカウンタとして
・ブロック文としてスコープを明示する
・式に付与したスコープを定義する

変数宣言はES6以降、varでの宣言は、letやconstで宣言するようになりました。
letは再定義不可の変数で、constは再代入不可の変数になります。

また、ループ内でのカウンタの利用としては、

for (var i = 0; i < x; i++) {

といったループの箇所で

for (let i = 0; i < x; i++) {

という記述になるような書き方になります。
また、letで宣言するより前の箇所で変数を使用するとundefinedになり、これはvarで宣言した時も同様になります。

letキーワードはES6から策定され、現在の開発ではvarよりもletを使うほうが主流の書き方になっています。
varでも動作することはしますが、今後の開発ではletで統一します。

RegExpオブジェクト

Posted コメントするカテゴリー: javascript

概要

javascriptで正規表現を取り扱うRegExpオブジェクトについて、調べてみます。

RegExpオブジェクトを使うとき、次の使い方があります。
・RegExp()コンストラクタ
・RegExpプロパティ
・RegExpメソッド

それぞれどういった使い方をするものか、順番に見ていきます。

RegExpコンストラクタについて

1つ(または2つ)の引数をもとに、RegExpオブジェクトを生成します。
正規表現を1番目の引数に指定し、2番目の引数は正規表現のフラグを書きます(省略可能)。

例として、RegExpコンストラクタの生成をしてみます。

let regcon = new RegExp("\\d", "g");
console.log(regcon);

上記のRegExpコンストラクタは数値にマッチする場合(フラグはg)を意味します。
「\」を2重に書く意図はエスケープ文字として動作する為、「\d」を正しく処理する為に2重に書きます。
また、ここで定義した「regcon」は正規表現オブジェクトとも言えます。
この正規表現オブジェクトを元に正規表現の処理を行います。

上記の例以外では、次のように書くことも可能です。

let regcon2 = new RegExp("BC", "g");
console.log(regcon2);

これは単純に文字列「AB」に対してマッチする場合の書き方です。
実際にマッチさせる場合のプログラムはこの投稿の後半にサンプルを書いてみます。(↑の記述だけでは、コンストラクタを生成しただけなので、何も処理を行いません)

RegExpプロパティについて

RegExpプロパティについて調べてみます。
javascriptのバージョンが年々アップされており、使用を推奨するプロパティと非推奨のプロパティが変化してきています。
詳しい解説は、こちらに解説されているので、詳細は割愛します。

RegExpプロトタイプオブジェクトのプロパティは次のようなものがあります。

RegExp.prototype.constructor
RegExp.prototype.flags
RegExp.prototype.dotAll
RegExp.prototype.global
RegExp.prototype.ignoreCase
RegExp.prototype.multiline
RegExp.prototype.source
RegExp.prototype.sticky
RegExp.prototype.unicode

また、非推奨のプロパティはこちらにまとめられていますので、詳細は省略します。

RegExpメソッドについて

RegExpメソッドについて、調べてみます。
メソッドというだけあって、正規表現のパターンマッチを実行する為に書きます。

RegExpのプロトタイプオブジェクトの仕様を調べてみると、次のようなものがあります。

RegExp.prototype.compile()
RegExp.prototype.exec()
RegExp.prototype.test()
RegExp.prototype[@@match]()
RegExp.prototype[@@matchAll]()
RegExp.prototype[@@replace]()
RegExp.prototype[@@search]()
RegExp.prototype[@@split]()
RegExp.prototype.toSource()
RegExp.prototype.toString()

それぞれの詳細な解説はこちらを参照するとわかりますが、
この中で頻繁に使われるものは「exec()」と「test()」です。
exec()メソッドは引数に文字列を指定してパターンマッチングを行います。
パターンマッチした場合は、match()メソッドと同様に配列を返し、
マッチしなかった場合はnullになります。

また、test()メソッドは引数に指定された文字列が、指定された正規表現とマッチするかどうかを返します。
マッチする場合はtrueを返し、しない場合はfalseを返します。
(詳しい仕様上の説明はここでは割愛します )

実際にサンプルを書いて動かしてみます

ここまでのRegExpの説明を元に簡単なサンプルを書いて動作させてみます。

次のサンプルコードを実行します。

let str3 =  "abc5def";
let regcon3 = new RegExp("\\d", "g");
console.log(regcon3.test(str3));

let str4 =  "abcdef";
let regcon4 = new RegExp("\\d", "g");
console.log(regcon4.test(str4));

結果は

true
false

という結果になります。
ここで出てくるtestはRegExpのメソッドとして機能します。
正規表現オブジェクトは文字列のパターンマッチングを行っています。
動作としてはmatch()となり、文字列の中に数値が含まれている場合にtrueが返ります。

では、上記の例で書いているtestメソッドをexecメソッドに変えて試してみます。

let str5 =  "abc5def";
let regcon5 = new RegExp("\\d", "g");
console.log(regcon5.exec(str5));

let str6 =  "abcdef";
let regcon6 = new RegExp("\\d", "g");
console.log(regcon6.exec(str6));

出力結果は次のようになります。

Array [ "5" ]
null

メソッド名の他は全て同じコードでしたが、メソッドがexecに変わった後の出力結果は異なります。

testメソッドでは文字列の検索をして存在の有無を判定結果を返していることに対し、
execメソッドでは検索でヒットした文字列を返しています。

この例と同様に、searchメソッド、replaceメソッド、splitメソッド等、RegExpオブジェクトとともに使用できるメソッドがあり、状況に応じて書きます。

パターンマッチング用文字列メソッド

Posted コメントするカテゴリー: javascript

概要

正規表現を使って、文字列を任意の条件でマッチさせて、検索や置換等を行う方法を試してみます。

パターンマッチによる検索

javascriptで文字列操作をする場合、非常に多岐に渡る方法があり、ここで一部を試して見ます。

下記のサンプルコードを書きます。

//文字列検索(対象文字列内のeの出現位置を返す)
let pattern_str = "abcdef";
console.log(pattern_str.search(/e/i));

//出現しない場合は-1を返す
let pattern_str2 = "abcd";
console.log(pattern_str2.search(/e/i));

出力結果は、それぞれ

4
-1

になります。
前者は「対象文字列内のeの出現位置を返す」ので、文字列eは対象文字列中の4つめを示します。
後者はマッチするものが無かった場合に-1になります。

パターンマッチによる置換

次に置換を試してみます。

//置換(文字列eが一致する場合、その文字を任意の文字列に置換する)
let pattern_str3 = "abcdef";
console.log(pattern_str3.replace(/e/i, "y"));

出力結果は

abcdyf

になります。
これは単純に文字列中の「e」がヒットし、それを「y」に置き換えています。

パターンマッチで検索したものを取得し、配列化する

次に文字列中にある条件のものを全て配列として返す場合です。

//正規表現で一致した結果を配列で返す
let pattern_str4 = "abc5def7";
console.log(pattern_str4.match(/\d/g));

出力結果は

Array [ "5", "7" ]

になります。(firefoxのログで確認した場合)

「\d」は数値を表し、パターンマッチ時にgオプションをつけることで、対象文字列中の全てのヒットした文字列に一致した文字列を返します。

簡単なサンプルのみになりますが、その他の文字列操作の正規表現は代表的なもので次のものがあります。

charAt
slice
split
substr
substring
toLowerCase
toUpperCase

これらはStringオブジェクトのメソッドなので、正規表現での文字列操作と合わせて使うと有効です。

正規表現のフラグ

Posted コメントするカテゴリー: javascript

概要

正規表現でパターンマッチする際に、フラグを指定することができます。

以下の3つのフラグがあります。

i
g
m

それぞれ、
iは大文字、小文字の区別をしない
gは一致する文字列をすべてにマッチ
mはマルチラインでマッチ

という意味になります。

実際にコードを書いて試してみます。

let test = /abcdef/;
let pattern_str = "ABCdef";
console.log(pattern_str.match(test));

let test2 = /abcdef/i;
let pattern_str2 = "ABCdef";
console.log(pattern_str2.match(test2));

let test3 = /abcdef/i;
let pattern_str3 = "ABCdefabcdef";
console.log(pattern_str3.match(test3));

let test4 = /abcdef/ig;
let pattern_str4 = "ABCdefabcdef";
console.log(pattern_str4.match(test4));

出力結果はそれぞれ以下のようになります。(firefoxの場合)

null
Array [ "ABCdef" ]
Array [ "ABCdef" ]
Array [ "ABCdef", "abcdef" ]

フラグのiとgをつけた場合、大文字小文字の区別をしないケースと、
対象の文字列中に複数回マッチするケースにそれぞれマッチすることがわかります。

正規表現のマッチ位置指定

Posted コメントするカテゴリー: javascript

概要

文字列のパターンマッチの方法として、前回の投稿は任意の文字列にマッチする方法を試しました。
正規表現を駆使すると文字列以外のマッチも多く使われます。

具体的には次の条件指定があります。

\s     //空白の文字列
\b     //単語の境界
\w     //任意の単語(単語文字列)
\W     //任意の単語文字以外の文字(境界)
^      //先頭
$      //末尾

次のサンプルコードを書いてみます。

//先頭と末尾を指定
let test = /^abcdef$/;

let pattern_str = "abcdef";

console.log(pattern_str.match(test));         //マッチする



//先頭(単語境界の区切り文字)を指定
let test2 = /\babcdef/;

let pattern_str2 = "abcdef";

console.log(pattern_str2.match(test2));       //マッチする



//単語境界以外(\B)を指定
let test3 = /\Babcdef/;

let pattern_str3 = "abcdef";

console.log(pattern_str3.match(test3));       //マッチしない



//単語境界以外(\B)を指定
let test4 = /\B[a]bcdef/;

let pattern_str4 = "testabcdef";

console.log(pattern_str4.match(test4));       //マッチする (abcdefとしてマッチ)



//単語境界以外(\B)を指定し、任意の文字aまたはkまたはwから始まるbcdefの文字列を指定
let test6 = /\B[akw]bcdef/;

let pattern_str6 = "testkbcdef";

console.log(pattern_str6.match(test6));       //マッチする (kbcdefとしてマッチ)




//空白の文字列を指定
let test7 = /\B[akw]bcdef/;

let pattern_str7 = "test kbcdef";

console.log(pattern_str7.match(test7));       //マッチしない




//空白の文字列(\s)を指定
let test8 = /test\sabcdef/;

let pattern_str8 = "test abcdef";

console.log(pattern_str8.match(test8));       //マッチする (test abcdefとしてマッチ)

出力結果は各console出力のコメント部分に記載した結果になります。

上記サンプルでは正規表現の一部で、詳しくは正規表現に特化した書籍やサイトで調べてプログラムを行います。

グループ化に関する記述

また、上記以外で、主にグループ化に使われる特殊文字の意味をまとめます。

|                 //左右どちらかに一致
(任意の文字)      //複数の項目をグループ化し、1つにまとめる(一致する文字を記憶する)
(?:任意の文字)    //複数の項目をグループ化し、1つにまとめる(一致する文字を記憶しない)
\N                //グループ化したもののN番目の文字列に一致する

「言明」について

さらに上記サンプルでは書いていなかった「言明」については、次のように書きます。

a(?=b)        //先読み言明          aにbが続く場合のみマッチ
a(?!b)        //否定先読み言明      aにbが続かない場合のみマッチ
(?<=b)a       //後読み言明          bにaが続く場合のみマッチ
(?<!b)a       //否定後読み言明      bにaが続かない場合のみマッチ

上記のルールを踏まえてサンプルコードを書いてみます。

//先読み言明
let test9 = /test(?=abcdef)/;

let pattern_str9 = "testabcdef";

console.log(pattern_str9.match(test9));       //マッチする(testとしてマッチ)



//否定先読み言明
let test10 = /test(?!abcdef)/;

let pattern_str10 = "testabcdef";

console.log(pattern_str10.match(test10));     //マッチしない



//後読み言明
let test11 = /(?<=abcdef)test/;

let pattern_str11 = "abcdeftest";

console.log(pattern_str11.match(test11));

//chromeではマッチする      ["test", index: 6, input: "abcdeftest", groups: undefined]
//firefoxではエラーになる   (SyntaxError: invalid regexp group)



//否定後読み言明
let test12 = /(?<!abcdef)test/;

let pattern_str12 = "abcdeftest";

console.log(pattern_str12.match(test12));


//chromeではマッチしない    ["test", index: 6, input: "abcdeftest", groups: undefined]
//firefoxではエラーになる   (SyntaxError: invalid regexp group)

出力結果は各consoleログの後に記載されているようになりますが、
注意が必要な点は、後読み言明の処理の場合、ブラウザの実行環境によりfirefoxの場合はエラーになります。

javascriptの実行処理がブラウザごとに完全に同一ではない点に注意する必要があります。

代替表現、グループ化

Posted コメントするカテゴリー: javascript

概要

正規表現の代替表現について、試してみます。
前回の投稿で下記のようなサンプルコードを書きました。

//先頭がaのものにマッチ
let test = /^a/;
let pattern_str = "abcdef";
console.log(pattern_str.match(test));

let pattern_str2 = "bcadef";
console.log(pattern_str2.match(test));

上記の場合は先頭がaの場合にマッチするという動きなので、
前者がマッチし、後者はマッチしません。

では、「先頭がaもしくはbのものにマッチする」という必要がある場合はどのように書くかというと、|を使います。
実際に書いて試してみます。

//|を使ってaとbの両方にマッチさせる
let test = /^a|^b/;

let pattern_str3 = "abcdef";
console.log(pattern_str3.match(test));

let pattern_str4 = "bcadef";
console.log(pattern_str4.match(test));

上記の出力の場合、出力結果は次のようになります。

Array [ "a" ]
Array [ "b" ]

|を使用すると一度のパターンマッチで複数の条件にマッチさせることができます。

グループ化

次に括弧を使った正規表現を試してみます。
括弧を使うとパターン文字列をグループ化してマッチさせることができます。

簡単な例を書いてみます。

let test_pattern = /a(b)/;
let str = "abcdef";
console.log(str.match(test_pattern));

出力結果は

Array [ "ab", "b" ]

になります。これはaと括弧内のbを組み合わせたabにマッチした結果と、
括弧内をグループ化してマッチした結果がそれぞれ出力されています。

次に上記の例を少し変更して次のコードを書いてみます。

let test_pattern4 = /a(b|c)/;
let str4 = "acbdef";
console.log(str4.match(test_pattern4));

let str5 = "abcdef";
console.log(str5.match(test_pattern5));

出力結果は

Array [ "ac", "c" ]
Array [ "ab", "b" ]

になります。
この場合は、/a(b|c)/と記述することで、aの後に続く文字列として、括弧内のabまたはacにマッチし、またそれぞれbとcもマッチします。

パターンの繰り返し

Posted コメントするカテゴリー: javascript

概要

正規表現の繰り返しについてです。
パターンマッチの対象文字列が任意のn桁の場合、n桁分のマッチ条件を正規表現で書くこともできますが、効率が良くないです。
その場合に繰り返し文字をマッチする為の記述にします。

具体的には次のような書き方をします。

{n, m} //n回からm回まで繰り返す
{n, }  //n回以上繰り返す
{n}    //n回のみ繰り返す
?      //0回または1回のみ
+      //1回以上繰り返す
*      //ゼロ回以上繰り返す

実際にコードを書いて動かしてみます

簡単なサンプルコードを書いてみます。

let test = /^a/;
let pattern_str = "abcdef";
console.log(pattern_str.match(test));

これを動かしてみると出力は「Array [ “a” ]」という結果になります。
これは対象の文字列の先頭がaだった為にマッチしています。

次に対象の文字列を少し変えてみます。

let test2 = /^a/;
let pattern_str2 = "bcadef";
console.log(pattern_str2.match(test2));

この場合は、出力は「null」になり、何もヒットしません。
先頭の文字列がbなので、ヒットしないというシンプルな動きになります。

では「n回からm回までを繰り返す」という例を書いてみます。
上記のサンプルを変更して次のコードを書いてみます。

let test3 = /a{4,7}/;
let pattern_str3 = "bcaaadef";
console.log(pattern_str3.match(test3));

出力は「null」になります。
これは文字列aが4回から7回まで繰り返している場合にマッチします。
対象となる文字列の中にaは3回繰り返していますが、この場合の条件には一致しないのでnullになりました。

では、さらに上記のコードを次のように書いてみます。

let test4 = /a{4,7}/;
let pattern_str4 = "bcaaaadef";
console.log(pattern_str4.match(test4));

この場合は、出力が「Array [ “aaaa” ]」になりました。
aが4回繰り返しているので、「4回から7回まで繰り返している」という条件にマッチする為です。

さらに次のコードを書いてみます。

let test5 = /a{4,7}/;
let pattern_str5 = "bcaaaaaadef";
console.log(pattern_str5.match(test5));

出力は「Array [ “aaaaaa” ]」になります。
aが5回記述されている場合としてマッチします。

同様に、aが6回記述されている場合、7回記述されている場合もマッチします。

では、aが8回記述されている場合はどうなるのか。
次のように書いてみます。

let test6 = /a{4,7}/;
let pattern_str6 = "bcaaaaaaaadef";
console.log(pattern_str6.match(test6));

出力は「Array [ “aaaaaaa” ]」になります。
対象の文字列にaが8個記述されている場合でも、「4回から7回」記述されているというパターンとしてマッチする。という動作になりました。

実際にコードを書いて動かした結果

正規表現のパターンマッチの条件一つとっても、やって試してみると動きがよくわかります。
上記の「4回から7回までの繰り返しのマッチ」ではaが8個記述されている場合でも、aが7回繰り返されているという条件でマッチすることがわかります。
文章で書くと「7回まで」という表現が紛らわしいですが、対象の文字列中に7回繰り返されているという条件が厳密に実行されることがわかります。