JavaScriptで配列を直接書くのが辛い時は、script要素に文字列を埋め込めば良いかもしれない


## 概要

例えば、JavaScriptでいろんな台詞を表示したいとします。
すると、大抵は台詞のデータを配列に書くことになると思うのですが…

```js
var dialogueList = [
  "あいうえお",
  "かきくけこ",
  "さしすせそ",
  "たちつてと",
  "なにぬねの",
  "はひふへほ",
  "まみむめも",
  "やゆよ",
  "らりるれろ",
  "わゐゑを",
  "ん"
];
```

こうなります。
台詞1つ追加する度に、引用符にカンマをつけなくてはなりません。
また、エスケープも考慮しなくてはなりません。
エディタに補完機能が無い場合は、インデントも手動で打たなくてはなりません[^インデントを手動で打つ]。

[^インデントを手動で打つ]: インデントはJavaScriptの実行とは無関係なので無理して打たなくても良いといえば良いのですが、読みにくさが増して保守性が悪化しますので大抵の人間は打つと思います。

これくらいなら数が少ないためまだ見やすいのですが、これが増えてくるともっと辛くなるかもしれません。
そこで、HTMLに改行区切りの文字を埋め込み、それを配列に変換するという方法を考えてみました。

## コード

```html
<!-- script要素でHTML内にデータを埋め込む -->
<script type="text/plain" id="dialogue_data">
  あいうえお
  かきくけこ
  さしすせそ
  たちつてと
  なにぬねの
  はひふへほ
  まみむめも
  やゆよ
  らりるれろ
  わゐゑを
  ん
</script>
```

```js
/*
 * HTMLでscript要素に埋め込んだ文字列を取得する。
 */
var dataText = document.getElementById("dialogue_data").textContent;

/*
 * 埋め込んだ文字列を配列に変換する。
 */
var dialogueList = dataText
  /* 改行ごとに分割し、配列化 */
  .split(/\r\n?|\n/)

  /* 配列の要素の前後からスペースを取り除く */
  .map(function(str) {
    return str.trim();
  })

  /* 空文字列の要素は配列から除外する */
  .filter(function(str) {
    return str !== "";
  });
```

```js:変数dialogueListに格納されるデータ
[
  "あいうえお",
  "かきくけこ",
  "さしすせそ",
  "たちつてと",
  "なにぬねの",
  "はひふへほ",
  "まみむめも",
  "やゆよ",
  "らりるれろ",
  "わゐゑを",
  "ん"
]
```

### 動作サンプル

[データを配列で書くのが面倒なので、改行区切りの文字列をHTMLに埋め込んで配列に変換する](http://output.jsbin.com/tixuho)

## 解説

原理は簡単です。
`script`要素にテキストデータを書き、そのテキストをJavaScriptで読み込んで配列に変換しています。

`script`要素はJavaScriptを使うための要素ではありますが、実は**JavaScriptではないテキストも埋め込めます**。
`script`要素の`type`属性を省略せず、JavaScriptのMIMEタイプではない値を書けば、WebブラウザはそれをJavaScriptとして実行しなくなります。

### 「JavaScriptのMIMEタイプではない値」とは？

*このセクションの説明について、著者は正しいと言い切れません。もし誤っていた場合は、コメントや編集リクエストで指摘してください。*

 [仕様書]では、「JavaScriptのMIMEタイプ」として以下のものが定義されています。

* `application/ecmascript`
* `application/javascript`
* `application/x-ecmascript`
* `application/x-javascript`
* `text/ecmascript`
* `text/javascript`
* `text/javascript1.0`
* `text/javascript1.1`
* `text/javascript1.2`
* `text/javascript1.3`
* `text/javascript1.4`
* `text/javascript1.5`
* `text/jscript`
* `text/livescript`
* `text/x-ecmascript`
* `text/x-javascript`

このような値が「JavaScriptのMIMEタイプ」になります。

ただし、これ以外の値であればなんでも良いかと言うと、そうではありません。
将来的に、WebブラウザがJavaScript以外の言語を`script`要素で読み込めるようにしてしまうかもしれません。
例えばGoogle Chromeでは、一時期[Dart]という言語を扱えるようにしようとしていました。もしそうなっていたら、以下のようなHTMLでDartが使えるようになっていたかもしれません。

```html
<script type="application/dart">
  main() {
    print('Hello, Dart!');
  }
</script>
```

すると、`type`属性に`application/dart`という値を指定して好き勝手なテキストを埋め込んでいるHTMLは、JavaScriptと同じように実行エラーを起こしてしまいます。

このような問題を防ぐため、`type`属性に指定する値は「言語として実行されないと定まっている値」を指定した方がいいと思います[^mimeの値に使える条件]。
例えば、[仕様書]で「言語として実行されない」と定義されている以下の値がこれに該当します。

* `text/plain`
* `text/xml`
* `application/octet-stream`
* `application/xml`

この記事で例示したような「独自形式のテキストデータ」の場合は`text/plain`が適切だと思います。

[^mimeの値に使える条件]: このような基準が明確に示された文書や資料、仕様を見たことは無いため、これはあくまで私の考えです。

### 外部ファイル化したいので`src`属性で読み込みたい…

このコードは**`src`属性での外部ファイル読み込みに対応していません**。
そもそも、独自形式のテキストデータを`script`要素で埋め込むこのやり方と`src`属性は相性が悪いため、私は推奨できません。
というわけでこの記事では、外部ファイル読み込みに対応させたコードは記載しないことにしています。

---

例えば以下の様な記述をした場合、Webブラウザは普通のJavaScriptと同じように「`dialogue-data.txt`」ファイルを読み込みます。

```html
<script type="text/plain" src="dialogue-data.txt" id="dialogue_data"></script>
```

しかし、これはJavaScriptとして処理されません。
そしてこの読み込まれた「`dialogue-data.txt`」ファイルのデータをJavaScriptは使用できません[^Webブラウザが読み込んだファイルは参照不可]。
このため、Ajax[^Ajax]で再び「`dialogue-data.txt`」ファイルを読み込み、そのデータを習得しなくてはなりません。

[^Webブラウザが読み込んだファイルは参照不可]: 少なくとも私は、「Webブラウザが勝手に読み込んだファイルのデータをJavaScriptで読む」方法を知りません。
[^Ajax]: 雑な表現をするならば、JavaScriptでファイルを読み込んでそのデータを取得する機能です。

つまり、二回も「`dialogue-data.txt`」ファイルを読み込まなくてはならなくなります。
通信量の無駄ですし、その分実行速度が遅れてしまいます。
この事から、この方法と`src`属性での外部ファイル読み込みは相性が悪いと私は考えています。

### Internet Explorer 8で動作しない

[上記のコード](#%E3%82%B3%E3%83%BC%E3%83%89)はECMAScript5で追加された関数を多用しているため、IE8[^IE8]では動きません。
代わりに[jQuery版](#jquery%E7%89%88)を使ってください。

[^IE8]: Internet Explorer 8の事は「IE8」と表記されることがよくあります。

&#x20;<del>ただ、そもそもIE8などというMicrosoftのセキュリティサポートが切れた負の遺産を使い続ける事そのものが問題なので、可能であればIE8で動かそうなんて考え自体を捨てましょう。</del>

## jQuery版

もしかしたら需要があるかもしれないので、jQueryでのコードも書いてみました。
HTMLは同じなので省略。

```js
/*
 * HTMLでscript要素に埋め込んだ文字列を取得する。
 */
var dataText = $("#dialogue_data").text();

/*
 * 埋め込んだ文字列を配列に変換する。
 */
var dialogueList = $.map(dataText.split(/\r\n?|\n/), function(str) {
  /* 配列の要素の前後からスペースを取り除く */
  var newStr = $.trim(str);

  /*
   * 要素が空文字列の場合、nullを返して配列から削除
   */
  if (newStr !== "") {
    return newStr;
  } else {
    return null;
  }
});
```

### 動作サンプル

[データを配列で書くのが面倒なので、改行区切りの文字列をHTMLに埋め込んで配列に変換する [jQuery版]](http://output.jsbin.com/zehiwe)

## 同じような例

このようなアイデアは、別に新しいものではありません。
例えば[Handlebars](http://handlebarsjs.com/)というテンプレートエンジンも、以下のような記述で独自形式のテンプレートをHTMLに埋め込んでいます。

```html
<script id="entry-template" type="text/x-handlebars-template">
  <div class="entry">
    <h1>{{title}}</h1>
    <div class="body">
      {{body}}
    </div>
  </div>
</script>
```

また[JSON-LD](http://json-ld.org/)でも、以下のようにして独自のJSONデータをHTMLに埋め込むことができます[^JSON-LDのMIMEタイプ]。

[^JSON-LDのMIMEタイプ]: JSON-LDの場合はMIMEタイプが`application/ld+json`で決まっているため、`type`属性はこの値になります。

```html
<script type="application/ld+json">
  {
    "@context": "http://schema.org",
    "@type": "Person",
    "name": "sounisi5011",
    "url": "http://qiita.com/sounisi5011"
  }
</script>
```

[Dart]: https://ja.wikipedia.org/wiki/Dart
[split関数]: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/split
[仕様書]: https://html.spec.whatwg.org/multipage/scripting.html#scriptingLanguages