Help us understand the problem. What is going on with this article?

【javascript】個人的な実装方針(2020年3月)

この記事について

今までほとんど手つかずだったJavascriptを実装しなければいけない時がやって来ました。
基本的なことがわかっていなかったので気づいたことをメモしていきます。
※個人的にJavaに少し慣れているのでJavaと比較している部分があります。
※記事内の「IE11ではサポートされていない」は2020年3月時点のサポート状況です。
※記事の内容に誤りがありましたらぜひ教えて頂きたいです。

はじめに考えること

まず(クライアントサイドの)Javascriptってブラウザで動かすので、ブラウザによってサポートしている、してないがあるということです。
例えばJava(サーバサイド言語)を使っていると、「Listの実型引数は省略できるか?」や「ラムダ式が使えるか?」はJavaのバージョンを意識すればいいだけなので苦じゃないですが、Javascriptの場合、IEで動くの?Safariで動くの?を意識しないといけません。
特にIEはサポートしていない仕組みが多いので注意する必要があります。
なので、実装する前に以下はまず考えなきゃ行けないと思いました。

  • 世の中で使われているブラウザは何なのか?
  • どのブラウザを対象とするのか?

メジャーに使われているブラウザ

「ブラウザ シェア」で検索するとウィキペディアがヒットし、その中でStatcounterというサイトが紹介されています。
このサイトで今どんなブラウザがどんな割合で使われているかの目安を確認しました。

世界でみたシェア(2020年1月)

ブラウザ シェア
Chrome 64.1%
Safari 17.21%
Firefox 4.7%
Samsung Internet 3.33%
UC Browser 2.61%
Opera 2.26%

https://gs.statcounter.com/browser-market-share

日本のみのシェア(2020年1月)

ブラウザ シェア
Chrome 46.65%
Safari 31.31%
IE 7.41%
Edge Legacy 5.69%
Firefox 5.36%
Samsung Internet 1.14%

https://gs.statcounter.com/browser-market-share/all/japan

Chromeつよいですね。
この一覧で世界の方は94%、日本の方は97%をカバーできます。
Samsung Internet、UC Browserは初めて見ましたが、それぞれ韓国、インドでよく使用されるブラウザのようです。
というかIE、Edgeって世界で見るとほぼ使用されてないんですね。この表見てびっくりしました。

参考にするドキュメント

「お、この関数便利そう」となった時はまず導入する前に以下の2つは見ておいたほうがいいと思いました。次章の、使うか使わないか判断の材料にしました。

MDN JavaScript リファレンス
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference
CanIUse
https://caniuse.com/

使いたかったけど使わなかったもの

上記のシェアを見ると、日本をターゲットに含める場合IE,Edgeは無視できないと思います。
特にIEは最新バージョンでも他のブラウザでは使える命令が使えないことが多いので慎重に取捨選択した方がよいです。
以下、使いたかったけど使わなかったものを挙げます。

let

重複して定義させたくない変数の定義に使用します。
基本的に同じ変数名で変数定義することは好ましくないと思うのでこのlet命令を使用したかったのですがIE11の場合forループのカウンタで使用不可でした。
変に部分対応の要素を入れてバグにつながるといやなので個人的にletは(というか部分対応の要素全般は)使用しないことにしました。

var arg1 = 'aaa';
var arg1 = 'bbb'; // 許容されてしまう
let arg2 = 'aaa';
let arg2 = 'bbb'; // argはすでに宣言済みです的なエラーを出してくれるので嬉しい

Rest parameters

可変長引数の関数を定義したい時に引数の前に「...」をつけることで可変長引数になります。
この「...」をつけた引数がRest parametersです。
配列としてまとめて引数を受け取るので関数内ではarg.lengthのように配列のプロパティが使用できます。
見た目が可変長であることがわかりやすいので使用したかったのですが、残念ながらIE未対応でしたので使用できませんでした。

function doSomething(str, ...args) {
  for(var i=0; i<args.length; i++){
    // do something
  }
}

そもそもJavascriptでは引数の数をチェックしません。
なので以下を実行してもすべてエラーにならずに正常終了してしまいます。
Javaに慣れているとびっくりですね。。。(変数定義もそうですが)

function doSomething(arg) {
  console.log(arg);
}
doSomething(); // undefinedだけど正常終了
doSomething('aaa'); // aaa
doSomething('aaa','bbb'); // aaa(後ろの引数は無視)

ただ、引数の数をチェックする方法はあってargumentsというオブジェクトを使用します。
このオブジェクトは関数呼び出しのタイミングで生成されるようで、引数に関する情報を保持しています。急に知らないオブジェクトが生成されているって変な感じがしますが、この方法を使えば引数チェックができます。

function doSomething(arg) {
  if(arguments.length < 3){
    console.error('missing argument 3 for doSomething().');
    return;
  }
  console.log('success');
  for(var i=0; i<arguments.length; i++){
    console.log(i + ':' + arguments[i]);
  }
}

doSomething('aaa'); // missing argument ...
doSomething('aaa','aaa','aaa'); // success

アロー関数

例えばaxiosという非同期通信を容易に行うためのライブラリがありますが、サンプルコードとして以下のようなソースをよくみます。レスポンスをthisオブジェクトにセットするだけです。(ちなみにaxiosはIE11対応しています)

アロー関数
axios.get('/user?id=101').then((response)=>{
    this.user = response.data;
})

これの=>がアロー演算子ですが、IEはサポートしていません。
よって無名関数形式に書き換える必要があります。

無名関数
var _this = this;
axios.get('/user?id=101').then(function (response) {
    _this.user = response.data;
})

その際に注意が必要なのは、アロー関数内と無名関数内ではthisが指す対象が異なるということです。
無名関数内でのthisは関数自体を示すためこのように別変数に避けておき、そちらにセットする必要があります。

クラス

Javaを実装する場合はクラスは必須ですが、Javascriptにとっては最近導入された仕組みのようです。一言にクラスと言っても、クラスの定義からコンストラクタ、Staticメソッド、Privateフィールドなどそれぞれでブラウザが対応しているか異なります。MDNを見ているとIEは軒並み使用できませんが、その他にも対応していない要素を持ったブラウザが結構あるようです。
よってクラス関連については使用しないことにしました。

MDN:Classes
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Classes

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

テンプレート文字列

Javaでいう、System.out.printf("残金は%d円です", 120);のようなprintf的な関数はなさそうでした。その代わり、シェルスクリプトのバッククォーテーションのようなものがあり、囲んだ文字列の中に${変数}があると変数や式の中を展開してくれます。
これもIEで使えません。

var price=120;
var format=`残金は${price}円です`;
console.log(format);//残金は120円です

でもprintf的な関数は欲しいのでこんな感じで作ってみました。

function printf() {
    if (arguments.length < 2) return arguments[0];
    var format = arguments[0];
    for (var i = 0; i < arguments.length; i++) {
        if (i == 0) continue;
        format = format.replace(/%[0-9]+/, arguments[i]);
    }
    return format;
}

console.log(printf('残金は%1円です。%2/%3/%4', 120, 2020, 3, 10));
//残金は120円です。2020/3/10

ここでもRest parameters使いたくなりますね。。

その他実装する上で考えたこと

単純な実装方法等でちょっと考えたことを書きます。

引数の多い関数をどうするか?

例えばですが引数が10個以上でかつ可変長みたいな関数だと、使用側では引数に設定する順序や個数を間違う可能性が高くなります。なので律儀に10個仮引数に定義するのではなく、1つのオブジェクト(連想配列)にまとめるのが良いと思いました。

2個必須+1つオプションの例
function doSomething(args) {
  if(Object.keys(args).length < 2){
    console.error('missing argument 2 for doSomething().');
    return;
  }
  if(args.id == null){
    console.error('id is required.');
    return;
  }
  if(args.name == null){
    console.error('name is required.');
    return;
  }
  console.log(args.name + ' is ' + args.id);
  console.log('success');
  if(args.address != null){
    console.log('address is ' + address);
  }
}

var params = {
  id : 101,
  name : 'tarou',
  // address:'tokyo', // オプション
  gender:'m' // 余計なものがあってもいい
}

doSomething(params);

この方法ならどの属性に何を設定したいか明白なのでよいですね。
オブジェクトはlengthプロパティを持たないためObject.keys(args)でいったんキーの配列を取得してそのlengthを取得するという方法になります。(ここでは引数の数チェックは不要ですが、サンプルとして載せています。)

ちなみにxxx == nullundefinedもしくはnullの場合にtrueになります。
undefinedとは変数の定義自体していない状態です。
なので上記のnameをコメントアウトしても、nullをセットしても'name is required.'がちゃんと表示されます。

DOM要素ごとサーバから受け取るとき

例えばhtmlエスケープ状態のaタグを受け取ってHTMLタグに戻したい時はアンエスケープが必要になります。
こちらを引用させて頂いています。
http://shanabrian.com/web/javascript/unescape-html.php

アンエスケープ
var unescapeHtml = function(target) {
  var patterns = {
        '&lt;'   : '<',
        '&gt;'   : '>',
        '&amp;'  : '&',
        '&quot;' : '"',
        '&#x27;' : '\'',
        '&#x60;' : '`'
  };
  return target.replace(/&(lt|gt|amp|quot|#x27|#x60);/g, function(match){
        return patterns[match];
    });
}

ちなみにこれを画面読み込み直後に呼んで特定のdivタグに設定したいという場合、javascriptを実装したことがないとこの関数はhtmlのどこで呼び出すの?となりますが、画面読み込み完了後にDOM要素を加工したいなら主に以下の2通りあると思います。

  1. bodyの閉じタグの直前で呼び出す
  2. headerタグ内でaddEventListener+DOMContentLoadedを使う

1の場合、全てのDOM要素が読み込まれた後なのでgetElementById等でアクセスできます。
bodyタグにscriptを入れたくない場合などは2の方法で実現できます。まだbody要素が存在しないので1と同じ方法ではアクセスできません。よって2のようにイベントリスナーを登録するという方法で実装する必要があります。

1.bodyの閉じタグの直前で呼び出す
</head>
<body>
    <!-- 省略 -->
    <div id="id"></div>
    <script>
        var text = '詳細は&lt;a href=&quot;/detail/101&quot;&gt;こちら&lt;/a&gt;';//詳細は<a href="/detail/101">こちら</a>
        document.getElementById("id").innerHTML = unescapeHtml(text);
    </script>
</body>
2.headerタグ内でaddEventListener+DOMContentLoadedを使う
<head>
    <!-- 〜省略〜 -->
    <script>
        //〜省略〜

        //DOM要素のロードが完了した後に実施される処理
        window.addEventListener('DOMContentLoaded', function(event) {
            document.getElementById("id").innerHTML = unescapeHtml(text);
        });
    </script>
</head>
<body>
    <!-- 〜省略〜 -->

innerHTMLを使用する場合はDOM要素を書き換えるので、設定する値は信頼できる提供元のみに限定する必要があります。(間違ってもユーザが入力した値に対して使用してはいけない。)
https://developer.mozilla.org/ja/docs/Web/API/Element/innerHTML#Security_considerations

関数とメソッド

全然意識したことなかったんですが、javascriptでは「関数」と「メソッド」は別物のようです。
関数をオブジェクトのプロパティの一つに設定する場合、それをメソッドと呼ぶようです。
ただ、実業務で明確に呼び分ける必要があるかというとそうでもなさそうですね。

関数の定義のいろいろ

上記を踏まえてちょっと調べると関数定義の仕方にも色々あることに気付きます。

//1.function命令による定義
function aaa(arg) {
  return 'arg:' + arg;
}
//2.関数リテラルによる定義
var aaa = function (arg) {
  return 'arg:' + arg;
}
//3.メソッドとして定義
var obj ={
  aaa : function (arg) {
    return 'arg:' + arg;
  },
  bbb : 'bbb',
  ccc (arg) {
    return 'arg:' + arg;
  }
}
//4.アロー関数による定義
var aaa = (arg) => {
  return 'arg:' + arg;
}

1はfunction命令で関数を直接定義する一番基本的な方法です。
2は「javascriptでは関数も型の一つ」ということを利用して、無名関数を定義して変数に代入する方法です。
3はオブジェクト(連想配列)の要素として関数を定義(=メソッド)する方法です。
上記の場合、obj.aaa(arg)、obj.ccc(arg)のようにアクセスします。
ただし、cccの省略記法はIE11ではサポートされていません。
4はアロー関数です。一番シンプルになりますが、IE11ではサポートされていません。

そもそもES2015ってなに?

ES2015、ES6ってjavascript調べるとよく出てきますが、
MDNを見るとこんなことが書いてあります。
https://developer.mozilla.org/ja/docs/Web/JavaScript

JavaScript の標準仕様は ECMAScript と呼ばれています。2012年以降、すべてのモダンブラウザーは ECMAScript 5.1 を完全にサポートしています。
〜中略〜
このバージョンは公式には ECMAScript 2015 と呼ばれていますが、最初は ECMAScript 6 や ES6 と呼ばれます。それ以降、 ECMAScript 標準は年単位でリリースされています。

要するにjavascriptの仕様はECMAScript(ES)で決まっていて、ES5.1はだいたいのブラウザに対応しているけど、ES6(ES2015)は対応してない仕組みがまだかなりあるよ、ということのようです。

あとになってきづきましたが、

ES6で初登場
letや、rest parameters、

ES2020(latest draft)で初登場
アロー関数、クラス、テンプレート文字列

なので、ES5.1のみ使用する、で割り切ってもよかったですね。

Babel

とは言っても、IE11もサポートしたいし、やっぱりES6で実装したい場合もあるかもしれません。
そういう場合は「トランスコンパイラー」というツールを利用することで過去形式に変換してくれるようです。トランスコンパイラーで一番メジャーなのがBabelです。
インストール後、こんな感じのコマンドでES6(ES2015)で実装されたjsファイルをES5形式に変換してくれるようです。

babel sample_es6.js -o sample.js --presets es2015

javascriptにもJavaDocみたいなものがある

JavaDocのようにJSDocというドキュメンメーションコメントがあります。
jsdocというツールをインストールし、実行するとhtmlの仕様書を生成してくれるようです。

インストールとjsdoc生成
npm install -g jsdoc
jsdoc sample.js

JSLint

アンチパターンで実装していないかを確認したいときに使えるツールです。
npmでインストールすることもできますが、ブラウザツールもありました。
今度試してみます。
http://jslint.com/

この他にもESLint、JSHintといった似たようなツールがあるようです。

おわりに

記事の内容に誤りがありましたらぜひ教えて頂きたいです。

Javascriptは奥が深いですね。
あとはテストツール(Jasmine.js等)も時間があったら調べてみようと思います。

atmospheri
レベルの低いエンジニア。 自分のために学んだ知識を記事に起こします。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away