1. Qiita
  2. 投稿
  3. JavaScript

旧石器時代のJavaScriptを書いてる各位に告ぐ、現代的なJavaScript超入門 Section1 ~すぐにでも現代っぽく出来るワンポイントまとめ~

  • 3941
    いいね
  • 8
    コメント
に更新
13

はじめに

ネットには様々な情報が溢れており、JavaScriptに関する情報も多数存在しております。

その中には、「今時こんな書き方しねえよ…」と思わずツッコミを入れたくなるような、本当に、本当に古い内容について書かれている古文書も存在します。 :skull:

そんな罠記事の情報に囚われてしまって、いつまで経っても現代的なJavaScriptが書けない皆さんのために、このシリーズの記事では、各セクション毎に分けて、旧石器時代の記述と、現代の記述を紹介する形で、文明開化をしていきたいという思いで記述する。

最初は、現在比較的メジャーなブラウザで一通り動作する「ECMAScript 5」までの内容に関してポエムを書き連ねていき、最終的には一連の内容を読むだけで「ES6(ES2015)」による新機能や、絶賛提案中の「ES7」の一部提案内容についても把握し、おおよそ現代人を育成することを目標とする。

…なんてめっちゃ堅苦しい表現しましたが、曖昧さを残しながらも分かりやすさ優先で説明していきたいと思います。
厳密なのが知りたい方は各々で、出てくるワードについてECMAScriptの仕様書やらMDNやらで確認してください :thumbsup:

また、このシリーズではECMAScript5を概ね対応するブラウザを対象としています。

もっと平たくいうと、IE8以下は切り捨てます。ご了承ください。

目次

Section1 ~すぐにでも現代っぽく出来るワンポイントまとめ~

ぶっちゃけこれを守るだけでも200X年の記述から201X年の記述に昇華できる。是非マスターしよう。

:sparkles: HTML5 :sparkles: を使う

しょっぱなからJavaScriptの話じゃなくてごめんなさい

「HTML5ってそもそもなんだ…?」って思う人がいるかもしれない。説明します。

まず前提知識として、普段何気なく記述しているHTMLは、20年の歴史の中で何度も何度も :sparkles: 改定 :sparkles: が行われているという事実を知ってほしい。また、XHTMLという亜種が誕生した過去もあるのだ。

その中でも特に、「HTML 4.01」と呼ばれるバージョンと「XHTML 1.0」と呼ばれるバージョンは、アホみたいに長い間生き残ったバージョンであり、未だにネット上にはこれらの情報が生きている。 :fearful:

そんな中、2014年に「HTML5」と呼ばれる、HTMLの現行最新改定が勧告されており、現在ではこちらの「 :sparkles: HTML5 :sparkles: 」でWebページを記述することが推奨されている。

その為、これを読んでいる現代人の皆さんは、今後Google先生に頼る際に、ぜひとも「 :sparkles: HTML5 :sparkles: 」と書かれている情報を信じて欲しい。

「HTML4.01」や「XML1.0」と書かれている情報はノイズと思いながらその情報を選別して欲しい。これで化石記述から脱却できるはずだ。 :smile:

なお、HTMLのバージョンが明示されて無く、HTMLのソースコードだけがネット上に転がっていることが多々ある。その場合は、HTMLの先頭を見て欲しい。

各種HTMLバージョンのDOCTYPE宣言一覧
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<!-- ↑これ(っぽいの)が書かれてるのがHTML 4.01 -->

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- ↑これ(っぽいの)が書かれてるのがXHTML 1.0 -->

<!DOCTYPE html>
<!-- ↑これが書かれてるのがHTML5。これを信じろ -->

要するに、先頭に書いてあるおまじないが :sparkles: シンプルなものがHTML5 :sparkles: の記述です。 Simple is BEST!

因みにこのおまじないのことを「DOCTYPE宣言」と呼ぶのですが、これから様々なソースコード情報を仕入れるときには、まず最初にこのDOCTYPE宣言がHTML5の情報であるかを確認するようにしましょう。 :heart:

document.writeは使わない :no_good:

ようやくJavaScriptの話です。

これを読んでいる方の中には、JavaScript側からHTML側にHTMLタグ形式テキストを出力する際にdocument.writeを使用する方がいらっしゃるかもしれません。

実は先ほどのHTML5の話とも少し関連するのですが、
:boom: HTML5では document.write(もしくはwriteln) の使用が推奨されていません。 :boom:

(気になる方は↓を読んでください。英文です。)
https://www.w3.org/TR/2011/WD-html5-20110525/apis-in-html-documents.html#document.write

↑を英語力のない私がガバガバな英語力で意訳しますと、
デバッグしんどい上に性能に響くからやめろ :anger: :anger: :anger:
です。

というわけで、document.writeは使わないで、可能な限りinnerHTMLを用いて解決するようにしましょう。

document.writeで記述された化石コード
<!DOCTYPE html>
<html lang="ja">
<head>
    <!-- 省略 -->
</head>
<body>
    <h1>今日の日付を表示します。</h1>
    <script>
        var date = new Date();
        var year = date.getYear();
        var month = date.getMonth()+1;
        var day = date.getDate();

        if(year < 1900) year += 1900;

        document.write("<p>今日の日付は" + year + "年" + month + "月" + day + "日です。</p>");
    </script>
</body>
</html>
現代的なコードに修正した(ほぼ同等の)動作
<!DOCTYPE html>
<html lang="ja">
<head>
    <!-- 省略 -->
    <script>
        document.addEventListener("DOMContentLoaded",function(eve){
            var date = new Date();
            var year = date.getFullYear();
            var month = date.getMonth()+1;
            var day = date.getDate();

            document.body.innerHTML += "<p>今日の日付は" + year + "年" + month + "月" + day + "日です。</p>";
        },false);
    </script>
</head>
<body>
    <h1>今日の日付を表示します。</h1>
</body>
</html>

ちなみにaddEventListenerって初見なんだけど!!!って思う方は後で説明しますので安心してください。

insertAdjacentHTMLを使って更に高速化 (16/01/18 18:50追記)

上のままでも一応動くのですが、innerHTMLは低速です。
今回のような場面では可能な限りinsertAdjacentHTMLの方を使いましょう。

現代的なコードに修正した高速な動作のコード
<!DOCTYPE html>
<html lang="ja">
<head>
    <!-- 省略 -->
    <script>
        document.addEventListener("DOMContentLoaded",function(eve){
            var date = new Date();
            var year = date.getFullYear();
            var month = date.getMonth()+1;
            var day = date.getDate();

            document.body.insertAdjacentHTML("beforeend", "<p>今日の日付は" + year + "年" + month + "月" + day + "日です。</p>");
        },false);
    </script>
</head>
<body>
    <h1>今日の日付を表示します。</h1>
</body>
</html>

これが完全版です。insertAdjacentHTMLについて知らない方はMDN等で確認しましょう。
element.insertAdjacentHTML - MDN

on○○イベントを(極力)使わない :no_good:

その中でも特にwindow.onloadは使わないようにしましょう。という話。

Webページを見ている人間が何らかのアクションをした時(ページにアクセスした時とか、要素(ボタン等)を押した時とか、画面をスクロールした時とか)、そのタイミングで実行したい時にイベントを使うと思います。

その際に、イベントを登録する方法に、大きく分けて2つ、「on○○」で登録する形と、「addEventListener("○○")」で登録する形と存在します。

さてここで大前提として、on○○には、最大1つしかメソッドを登録できないという問題があります。 :thumbsdown:
window.onloadの例で見てみましょう。

onloadイベントによる登録
// この代入が無視(上書き)される
window.onload = function(){
    alert("読み込み終わったよ!");
};

// こっちの代入だけが実行される
window.onload = function(){
    alert("すまん↑のやつ上書きしちまったわガハハw");
};

window.onloadが単なるプロパティ(変数みたいなもの)なので、再代入されたら上書きされるという話です。当たり前ですね。

いやいやそんな、鳥頭じゃないんだから普通は上書きなんてしないやろw

って思うかもしれませんが、それが出来るのは、
外部ライブラリを一切使用せず
最初から最後まで全て一人でコードを完成しきって
全コードのイベント代入状態を把握している
という完全なるプロにだけ許されています(要するに無理です) :fearful:

更に言えば、それだけのプロならわざわざ縛りプレイをする必要もないですよね。

それでは困る。ということで :sparkles: EventTarget.addEventListener :sparkles: が登場しました。

これは、従来の「代入」をやめて、「関数を登録する」という形で実装することで、再代入による上書きを抑制し、「登録された順番に順次関数を実行する」という風にしたものです。

…という堅苦しい説明は置いといて、以下のように書き換えれば両方の登録が実行できるようになります。

addEventListenerによる登録
// この登録が一番最初に実行される
window.addEventListener("load",function(eve){
    alert("読み込み終わったよ!");
},false);

// こっちの登録は一番最初に実行が完了した段階で続けざまに実行される
window.addEventListener("load",function(eve){
    alert("こっちの読み込みも実行できるんですわガハハw");
},false);

書き換え方法について説明します。

  1. 「window.on○○」を「window.addEventListener("○○")」に変える
  2. 代入してた関数をaddEventListenerの第二引数に指定する
  3. 代入してた関数の第一引数を「eve」にする
  4. addEventListenerの第三引数をfalseにする

以上です。

addEventListener第三引数の指定について (16/01/18 19:36追記)

「第三引数をfalseにするってなんでだよ」って思うかもしれません。結論からいうとfalseにしておくと幸せな事が多い、です。
(たまに明示的にtrueにする話もありますがそれはかなり高度なのでここでは触れないとして)

この辺りを詳しく知りたい方は↓のページが綺麗にまとまっていてオススメです。
DOMイベントのキャプチャ/バブリングを整理する 〜 JSおくのほそ道 #017

(余談)attachEventの扱いについて

その昔、古いIEは :sparkles: addEventListener :sparkles: を搭載しておらず、代わりに :poop: attachEvent :poop: という名前の、似たような、でも微妙に挙動が違うメソッドを持っていたので、Web開発者はそれを用いて代用していましたが…

安心してください、古いIEは全滅しましたよ :smile: :thumbsup:

や、まあ厳密には数%生きてるんですが、現状気にしなくて良いレベルになりました。
また、Microsoft側のサポート宣言により、(事実上)IE11未満はよほどの特殊条件でない限りは相手にしなくても良くなりました。
Internet Explorer サポートポリシー変更の重要なお知らせ - Microsoft

なので安心してaddEventListenerを使いましょう :laughing:

そして関連することですが、「window.onload」はほとんどの需要において「DOMContentLoaded」イベントで代用することが出来ます。

loadとDOMContentLoadedによるそれぞれのDOM取得例
<!DOCTYPE html>
<html lang="ja">
<head>
    <!-- 省略 -->
    <script>
        // loadによるイベント登録
        window.addEventListener("load",function(){
            var $text = document.getElementById("text");
            alert("onload: <p>タグの中身は" + $text.innerHTML);
        },false);

        // DOMContentLoadedによるイベント登録
        document.addEventListener("DOMContentLoaded",function(eve){
            var $text = document.getElementById("text");
            alert("DOMContentLoaded: <p>タグの中身は" + $text.innerHTML);
        },false);
    </script>
</head>
<body>
    <p id="text">メロスは激怒した。以下略</p>
    <div>
        <img src="滅茶苦茶容量がでっかいファイル.png">
    </div>
</body>
</html>

このように書き換えて何が嬉しいのか?という話ですが、実行タイミングに大きな違いがあります。

:snail: load:snail: イベントは、HTMLの読み込みが完全に完了されたタイミングで実行されるため、「ページ内のすべての画像等が読み込み終わった段階で発火します」
要するに滅茶苦茶遅いんです。 :sweat_drops:

一方で、 :racehorse: DOMContentLoaded :racehorse: は、HTMLの構造の解析が完了したタイミングで実行されるため、「画像等の読み込み完了を待つ事無く、DOMを触れられるようになった段階で発火します」
要するに滅茶苦茶早いんです :dash:

Webページで使用するJavaScriptは、殆どの用途においてDOMの解析が終わった段階で要求を応えられる案件ばかりなので、表示速度のためにも極力 :racehorse: DOMContentLoaded :racehorse: を使用して記述していくようにしましょう。

:warning: ただし、 :racehorse: DOMContentLoaded :racehorse: は、その性質上画像の幅を取得する前に発火するため、画像の幅を利用したレイアウト調整をする際は:snail: load:snail: イベントを使ってください!

loadイベントを利用した画像幅に合わせたレイアウト
<!DOCTYPE html>
<html lang="ja">
<head>
    <!-- 省略 -->
    <style>
        #imgContainer {
            background-color: black;
        }
    </style>
    <script>
        // loadによるイベント登録
        window.addEventListener("load",function(){
            var $imgContainer = document.getElementById("imgContainer");
            var $img = $imgContainer.querySelector("img");
            alert($img.width); // 863 ←あくまで一例。今回は863pxの幅の画像だった

            // 画像の幅を利用してスタイルを指定する例
            $imgContainer.style.width = ($img.width + 20) + "px";
        },false);

        // DOMContentLoadedによるイベント登録
        window.addEventListener("DOMContentLoaded",function(){
            var $imgContainer = document.getElementById("imgContainer");
            var $img = $imgContainer.querySelector("img");
            alert($img.width); // 0 ←DOMContentLoadedだとこうなるんです!!!!!
        },false);
    </script>
</head>
<body>
    <div id="imgContainer">
        <img src="何らかの画像.png">
    </div>
</body>
</html>

Dateの一部メソッドは使わない :no_good:

詳しくは JavaScript の Date は罠が多すぎる
にとても良くまとめられているのでこれを読めば解決するのですが、

特に化石問題で話題になるのは「 :poop: Date.prototype.getYear() :poop: 」です。
ぶっちゃけた話、今時getFullYear()を搭載していないブラウザなんて存在しないし、存在したとしても窓から放り投げて捨ててしまって構わないシロモノです。
従って、絶対に :sparkles: Date.prototype.getFullYear() :sparkles: の方を利用しましょう。

絶対にDate.prototype.getFullYear()しような
var date = new Date();
alert(date.getFullYear()); // 2016(記事書いてる段階では)

setTimeoutやsetIntervalの第一引数に文字列を渡さない :no_good:

何故かわからないのですが、setTimeoutやsetIntervalについてネットで文献を探すと、古い記述であるsetTimeout("funcName()")みたいな記述が見当たります。

実は、setTimeoutやIntervalの第一引数には、関数への参照をそのまま渡すことが出来ます。そして現代的にはこちらの方が一般的なので、文字列を渡すことはやめましょう。

化石のsetTimeoutの指定方法
setTimeout("hello()",1000);

function hello(){
    alert("こんにちは!");
}
現代的なsetTimeoutの指定方法
setTimeout(hello,1000);

function hello(){
    alert("こんにちは!");
}

(余談)文字列を第一引数に渡した時の挙動

第一引数に文字列が渡された時は、それをFunctionオブジェクトでラップする処理が挟まります。

Functionオブジェクトでラップするってなんぞやっていう話ですが、実はFunction関数からnewを用いてインスタンスを作ると、文字列を評価することが出来るのです。(挙動としてはevalに近いです)

Functionオブジェクトで文字列を評価して処理をする
// 実は関数はFunctionインスタンスを作ることで文字列から生成することが出来るのだ
var f = new Function("return 18");
alert(typeof(f)); // "function"
alert(f()); // 18

// 引数ありのものは引数名を最初に指定する
var g = new Function("a", "b", "return a + b");
alert(g(1,2)); // 3

これを用いて、setTimeoutやsetIntervalでは、第一引数が文字列だった時は、ソースコードの文字列とみなして、引数を1つも取らない関数オブジェクトに変換するのです。

(引数を受け付けない)setTimeoutの擬似コード
function setTimeout(a,b){
    var func = a;
    if(typeof(a) !== "function"){
        func = new Function(a.toString());
    }

    // b秒経ったら以下が実行される
    func();
}

for inループは(極力)使わない :no_good:

正しい使い方をすれば使っても良いんですがねぇ…

実はfor inループは「DataDescriptorのenumerableがtrueになってるString型のキーを、prototypeチェーンを辿りながら全て列挙する」という性質を持ちます。

↑の説明は「日本語でおk」になると思いますので、どういうことが起きるか説明したコードを示します。

for-inループの罠
Object.prototype.gao = "がおがおがお~~~www";

var obj = {a:1, b:2};
for(var key in obj){
    alert(key); // "a"や"b"と共に"gao"が列挙される
}

この問題は

  • そもそもprototypeを汚染しているのが悪い。汚染するな
  • definePropertyを用いてenumerableを明示的にfalseにすればいい
  • hasOwnPropertyを使ってチェックすればいい

などで解決しますが、どれも頭が痛くなるような話なので、ここではObject.keysとArray.prototype.forEachを組み合わせてシンプルに以下のように解決しましょう。

for-inループの代替方法
Object.prototype.gao = "がおがおがお~~~www";

var obj = {a:1, b:2};
Object.keys(obj).forEach(function(key){
    alert(key); // "a"や"b"のみが列挙される
});

この辺りは他にも色んな解決方法があるので気になる方は各自ググってください。
因みにこの記述はES7(後述予定)で絶賛提案中の機能を使えば劇的に楽になります。

evalは(極力)使わない :no_good:

という話はありますが、これはかなり古い時代から言われてきていることなので特に言及しなくても皆さんほとんど使わないと思います。

もしevalを多用しているようでしたら、今すぐやめてください。

JSONのデコードも、現代的なブラウザでは殆どが「JSON.parse()」で解決します。

JSON.parseによるJSONテキストのデコード
var text = JSON.stringify([
    {
        "name": "香風智乃",
        "age": 13,
    },
    {
        "name": "保登心愛",
        "age": 15,
    },
    {
        "name": "天々座理世",
        "age": 16,
    },
    {
        "name": "宇治松千夜",
        "age": 15,
    },
    {
        "name": "桐間紗路",
        "age": 15,
    },
]);

alert(typeof(text)); // "string"

// これは危険なコードだからこの書き方マジでやめろ
//var obj = eval(text);

// こっちを使いましょう
var obj = JSON.parse(text);

alert(typeof(obj)); // "object"
alert(obj[3].name); // "宇治松千夜"

その他どうしてもevalが使いたい場合、汎用的な用途でも、殆どが「new Function("文字列")」の方で代用できます。(コードは省略します)
(本当はこれすらもあまり使っていただきたくないですが…)

alertでデバッグしない :no_good:

筆者自身も今まで散々してただろ何言ってんだこいつは

この説明するまでは説明しやすさ重視でalertデバッグしていましたが、以降はconsoleオブジェクトを使います。

このページを見ている人の殆どの人が、PC上からJavaScriptのデバッグを行う際に「IE」「Firefox」「Chrome」「Safari」の中から少なくとも1つのブラウザを使用していると思います。

その方々はF12を押してみてください。大抵の方は画面下部に開発ツールが表示されるかと思います。

そして、(ブラウザによって表現が微妙に違うのですが)「コンソール」とか「Console」とか書いてあるタブを押してください。
これがコンソールウィンドウです。

alert関数では、画面上に小窓を出して表示していました。
実は、「console.log("Hello,World");」という風に、console.logに文字列を渡すと、このコンソールウィンドウにその出力を出すことが出来ます。

コンソールウィンドウへの出力
console.log("がおがおがお~~~www");

更に、alertでは表示することの出来なかった、オブジェクトの中身なども見ることが出来ます

コンソールウィンドウへの出力2
var obj = {name: "gao", age: 18};

// alertだと†[object Object]†闇が垣間見れる
alert(obj); // [object Object] (ブラウザ毎で表記ブレはある)

// console.logだとちゃんとオブジェクトの中身が確認できる
console.log(obj); // Object {name: "gao", age: 18} (ブラウザ毎でry)

どう考えてもデバッグ効率が上がるので、今後はalertデバッグではなくconsole.logデバッグを活用していきましょう。

:warning: ただし、IE8,9ではconsole.logの使用に気をつけてください
というのも、IE8,9はゴミブラウザなので、F12を押すまでの間、「console」オブジェクトが未定義であり、エラーを吐くのです。
【JavaScript】IE9以前でconsole.logを使用しているとハマる

この問題はIE10で改善されていますが、先に出した通り、依然として極々限られた環境相手にはIE9サポートを要求される場面が存在するので頭の片隅に入れておいてください。
ただし、通常はIE11以上を相手にすればいいと私は考えてますので、ガンガンconsole.logを使用していきましょう。というスタンスではあります。

余談ですが、Qiitaページ内も、IE9以下対策として以前はconsole.logを自前定義で上書き削除していましたが、今は使用できます。

:sparkles: Strict Modeを使う:sparkles: (16/01/19 17:00追記)

古代のJavaScriptでは、よく言えば「柔軟」、悪く言えば「ガバガバ」な記法が認められていました。
しかし、記述が自由すぎる故に、時としてデバッグが困難になる場面がありました… :astonished:

このような現状に対応するために、 :sparkles: Strict Mode :sparkles: というものが誕生しました。

Strict Modeについての説明は以下の記事がまとまっていてオススメです。
IE10 以下を切る場合の JavaScript チェックリスト - JS編 Strict Mode

また、こちらは記事がやや古めですが情報が綺麗にまとまっているのでこちらもおすすめです。
“use strict”(厳格モード)を使うべきか? - ANALOGIC

因みに最近のデベロッパー達が思うStrict Modeへの認識ですが、
StrictMode使わない人間に人権はない :anger:
という認識がほとんどです。(以下宗教戦争禁止)

ですので、Strict Modeの関数適用を積極的に使うようにしましょう。 :punch:
(グローバル適用の方はまだ難しい側面もありますが時間がきっと解決してくれるはず…。)

StrictModeの関数適用の例
(function(){
    "use strict";

    console.log(typeof(this)); // "undefined" ←"object"ではない!

    try{
        arguments.callee;
    }
    catch(err){
        console.log("arguments.calleeはStrictModeでは使えないよ");
    }
})();

:warning: IE9以下ではStrict Modeを利用できません。
ただし、あくまでも利用できないだけであって、エラーも何も出ずしれっと危険なコードが書けるというだけです。
普段はIE9以下のブラウザをメインでデバッグすることはない思われますので、あまり気に留めるところではないのですが一応気をつけてください。


というわけで、Section1についての内容はここまでとなります。
この記事を読んだ方はもれなく201X年並みのJS記述ができるようになりました。ぶっちゃけ世に出してもまあ恥をかかないレベルです。

これから先の内容は、更に話を進めて、2015年以降の最新情報を説明していきます。

次の話はこちらっ
旧石器時代のJavaScriptを書いてる各位に告ぐ、現代的なJavaScript超入門 Section2 ~CommonJSモジュールと仲良くなろう~