以前のResponsive Web PageでもWebの動的制御のためにJavaScriptを少し紹介していましたが、jQueryを使ったHTMLの制御のみでした。今回はJavaScriptをサーバでも使えるNode.jsを中心に実用性のあるサンプルコードをまとめてみたいと思います。最近AWSのLambdaでNode.jsを使ったソースを書いたこともあり、Lambda関連のサンプルコードも書く予定です。

このポストも随時更新して行く予定です。(放置中のポストが多い状態ですが…)

現在日付の取得

基本的な日付処理です。下記のサンプルではpadStart()を使って一桁の月と日の先頭に0を埋めています。但し、new Date()で取得した日付はnumber型のため、padStart()を使うにはstring型に変換する必要があります。

// 日付の取得
var now = new Date();
// JST(UTC + 9時間)に変換(必要に応じて)
now.setTime(now.getTime() + 32400000);
// 現在日付の取得
var nowTime = now.getFullYear() + "年" + 
(now.getMonth() + 1 + "").padStart(2, "0") + "月" + 
(now.getDate() + "").padStart(2, "0") + "日" + 
now.getHours() + "時" + now.getMinutes() + "分";
2019年04月30日19時53分

正規表現

正規表現とは文字の組み合わせを照会するためのパータンで、文字と記号を使って様々なパターンを表現することができます。下記は<name>タグで囲まれた文字列を取得するサンプルとなります。正規表現を扱う関数は色々ありますので、用途に応じて使うと便利です。

// 正規表現の取得
var regExp = /<name>([\s\S]*?)<\/name>/i;
// 上記と同様
var regExp = new RegExp("<name>([\s\S]*?)<\/name>", "i");
// マッチを検索
var match = regExpA.exec("<Name> Chino</Name>");
// マッチを編集
var errcd = match[0].replace(/<name>|<\/name>/gi, '').trim();
Chino

パターンに一致する文字を検索する関数はmatch()exec()があります。exec()は正規表現のオブジェクトを生成する必要がありますが、/で囲む方法とnew RegExp()を使う方法があります。上記のサンプルで使っている正規表現は以下の通りです。

  • [abc] abcのいずれか1文字に一致
  • \s 空白文字に一致
  • \S 空白文字以外に一致
  • * 直前文字の0回以上繰り返しに一致(ab*aabbなどに一致する)
  • ? 直前文字の0回か1回に一致(ab?aabに一致する)

因みに/正規表現/iinew RegExp()の第二引数はオプションフラグです。iは大文字と小文字を区別しないオプションで、gは文字列全体に対して何回もマッチングを行うオプションとなります。

Media Query

メディアクエリとは画面の解像度によりコンテンツの描画を変えられるCSS3のモジュールですが、javaScriptでもCSSでも同じ判定ができるmatchMediaが存在します。基本、メディアクエリはCSSで記述するものですが、アプリケーションが複雑になるとJavaScriptでも解像度の判定を行いたい場面が出てきます。ただ、Internet Explorer 9は対応されてないので注意が必要です。

var isMobile = false;
var mq = matchMedia('(max-width: 768px)');

function handleMediaQuery(mq) {
  if (mq.matches) {
    isMobile = true;
  } else {
    isMobile = false;
  }
}

mq.addListener(handleMediaQuery);
handleMediaQuery(mq);

matchMediaよりCSSの@media (max-width: 768px)と同じ設定を行い、解像度がブレイクポイントになったときに、関数handleMediaQueryが呼び出されてマッチすればモバイル、マッチしない場合はPCと判定します。下に記述したhandleMediaQuery(mq);は初期表示時のためです。

Promise

非同期処理で処理の実行順序を保つ方法としてコールバック関数が使われていますが、呼び出しの関数が増えれば増えるほど入れ子が多くなり、いわゆるコールバック地獄になってしまう問題があります。下記のプログラムがその例です。

function play(payment, callback) {
  if (payment >= 100) {
    callback(payment - 100, null);
  } else {
    callback(null, "お金が足りないよ!");
  }
}

play(202, function(change, error) {
  if (!error) {
    console.log("1回目の残金は" + change + "円です。");
    play(change, function(change, error) {
      if (!error) {
        console.log("2回目の残金は" + change + "円です。");
        play(change, function(change, error) {
          if (!error) {
            console.log("3回目の残金は" + change + "円です。");
          } else {
            console.log(error);
          }
        });
      } else {
        console.log(error);
      }
    });
  } else {
    console.log(error);
  }
});
1回目の残金は102円です。
2回目の残金は2円です。
お金が足りないよ!

100円単位で残金を減らしていくあまり意味のないプログラムですが、3回呼び出しただけで既に読みづらくなっています。このようなコールバック地獄を回避するために登場したのがPromiseです。

function play(payment) {
  return new Promise(function(resolve, reject) {
    if (payment >= 100) {
      resolve(payment - 100);
    } else {
      reject("お金が足りないよ!");
    }
  });
}

play(202).then(function(change) {
  console.log("1回目の残金は" + change + "円です。");
  return play(change);
}).then(function(change) {
  console.log("2回目の残金は" + change + "円です。");
  return play(change);
}).then(function(change) {
  console.log("3回目の残金は" + change + "円です。");
  return play(change);
}).catch(function(error) {
  console.log(error);
});
1回目の残金は102円です。
2回目の残金は2円です。
お金が足りないよ!

Promiseを使ってプログラムを作り直してみました。play関数ではPromiseオブジェクトに成功した場合のresolveと、失敗した場合のrejectを設定して返却します。呼び出しの際は成功した場合の処理をthen関数に、失敗した場合の処理をcatch関数に設定するだけで終わりです。呼び出し回数が増えても入れ子が発生しないため、コードも読みやすく保守性も向上されます。

async/await

Promiseのthen関数を使うことで処理を同期的に実行することが可能でしたが、awaitを使うとより簡潔に同期処理を書くことができます。使い方はPromiseの前にawaitを書くだけですが、asyncがついた関数内でしか使えないため注意が必要です。

function play(payment) {
  return new Promise(function(resolve, reject) {
    if (payment >= 100) {
      resolve(payment - 100);
    } else {
      reject("お金が足りないよ!");
    }
  });
}
async function allPlay() {
  var change = 202
  try {
    change = await play(change);
    console.log("1回目の残金は" + change + "円です。");
    change = await play(change);
    console.log("2回目の残金は" + change + "円です。");
    change = await play(change);
    console.log("3回目の残金は" + change + "円です。");
  } catch (error) {
    console.log(error);
  }
}

allPlay();
1回目の残金は102円です。
2回目の残金は2円です。
お金が足りないよ!

asyncが付いたallPlay関数を用意し、その中でplay関数を3回呼び出しています。呼び出しの前にawaitを付けているため、Promiseが終了するまで次のコードには進みません。また、try/catchでエラーのハンドリングを行っているため、途中でエラーになっても次のコードには進まず、catchのコードが実行されます。

await play(change).catch((error) => console.log(error));

もし、処理ごとに別のエラーハンドリングを行いたい場合は、上記のようにawait/catchパターンを活用して一行で書くことも可能です。

半角と全角文字変換

韓国語ではあまり意識していませんでしたが、日本語は半角と全角文字の使い分けが厳しく、特に全角の特殊文字入力が難しいスマートフォンでは住所の番地などの入力にすごく手を焼いていました。その不便を少しでも解決するために、半角もしくは全角のみの入力フォームなどは自動変換処理が必要だと思います。

function toSingleByteCharacter(value) {
  var singleByteCharacter = value.replace( /[A-Za-z0-9-!”#$%&’()=<>,.?_[]{}@^~¥]/g, function(s) {
    return String.fromCharCode(s.charCodeAt(0) - 65248);
  });
  return singleByteCharacter;
}

function toDoubleByteCharacter(value) {
  var doubleByteCharacter = value.replace( /[A-Za-z0-9-!"#$%&'()=<>,.?_\[\]{}@^~\\]/g, function(s) {
    return String.fromCharCode(s.charCodeAt(0) + 65248);
  });
  return doubleByteCharacter;
}

console.log(toSingleByteCharacter("Hello!"));
console.log(toDoubleByteCharacter("Good Bye!"));

Hello!
Good Bye!

上記のコードは単純に文字列を半角や全角文字に変換する関数のみとなります。入力時、フォーカスアウト時の変換処理はご自身で検索してみてください。仕組みはとても簡単で、半角文字の文字コードから65248番目の文字コードが全角文字になります。なので、関数に渡した文字の文字コードに変換し、65248を加算した文字コードを再度文字に変換します。逆に全角文字は65248を引いて同じく変換します。