1. Qiita
  2. Items
  3. JavaScript

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

  • 3364
    Stock
  • 9
    Comment
Stocked
  • dot
  • mAster_rAdio
  • shu_0115
  • tyamaz
  • WataruGodo
  • hihira
  • kysnm
  • rn404
  • gogyo
  • zukkun

はじめに

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

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

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

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

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

また、このシリーズでは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つしかメソッドを登録できないという問題があります。 :-1:
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: :+1:

や、まあ厳密には数%生きてるんですが、現状気にしなくて良いレベルになりました。
また、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モジュールと仲良くなろう~

Send an edit requestYou can propose improvements about the article to the author 💪
418 contribution
流し読みでコメントしてしまって恐縮ですが(もし見落としてたらすみません)、コンソールの章で IE8,9 についての注意点が触れられていたので、for-inのところでも「`Object.keys` と `Array.prototype.forEach` は IE8 では Polyfill が必要」ということが触れられていると親切かなって思いました(IE8 なんて無視したいのはヤマヤマなんですが)

すみません、脊髄反射的に変なコメントつけてしまったので訂正します。

コンソールの章で「IE8,9では」と注意書きがあるために、逆に他の章の内容はIE8で気をつけることが無いようにも読めてしまいます。IE8については切り捨てている旨を強調したほうが誤解が無いかなと感じました。

ちなみに蛇足ですが、「for inループは(極力)使わない」と「そもそもprototypeを汚染しているのが悪い。汚染するな」は結構表裏一体で、「自分は for in ループ使ってない(か、hasOwnProperty 使ってる)から prototype 汚染しても大丈夫だぜヒャッハー」とかやってて「for in ループを使っている他社製ライブラリが動かなくなった」という経験があることを書き添えておきます。(その prototype 汚染というのが Array.prototype への forEach などの定義だったのがまたなんとも悲しいオチなのですが…)

5249 contribution

コメントありがとうございます。順に答えていきますね。


「Object.keys と Array.prototype.forEach は IE8 では Polyfill が必要」

についてですが、仰るとおりです。

しかし、冒頭で述べてますように

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

ということで、この記事自体が「ECMAScript 5 が動作する」という前提ですべての話を進めておりました。JSON.parseのあたりの下りも同様です。

IE8,9の対応のくだりは、IE8は(ECMAScript 3相当なので)切り捨てる前提です。
そして、IE9に関しては(ECMAScript 5.1相当なので)切り捨てないので、対策を講じる。という流れでございます。

(余談で。記事中では言及しておりませんが、先日IE8のMicrosoft側のサポートが完全に切れた、というのもこの記事を書いた動機であったりします)

ただ、説明不足(というより、解釈に幅がある表現であった)であったということは感じました。はじめにの部分にECMAScript 5が動作する前提で話を勧めるということを明記致します。指摘ありがとうございました。


「自分は for in ループ使ってない(か、hasOwnProperty 使ってる)から prototype 汚染しても大丈夫だぜヒャッハー」とかやってて「for in ループを使っている他社製ライブラリが動かなくなった」という経験があることを書き添えておきます。

この経験は私もございます。
私の考えから述べますと「当然ながら無慈悲なprototype汚染も悪いが、と同時に他社ライブラリがprototype汚染について考慮していないのも悪い」というスタンスではあります。

といいますのも、例えば他社ライブラリAがprototype汚染をガンガンしているせいで、他社ライブラリBが動作しなくなった。というケースを想定しましょう。

この時、誰が悪いか、というのは中々結論付けるのは難しいと思います

  • prototype汚染は迷惑行為、どう考えてもAが悪い
  • (やりすぎなのはともかくとして)単純なprototype汚染程度で動かなくなるBが悪い
  • AとBを組み合わせて運用しようとした自分が悪い

というわけでこの記事中では、「prototypeはどこかで汚染されているものとする」という性悪説を採用して、「少なくとも自分の記述する範囲では確実にイテレーション出来るようにする」ということで落としました。

少し倫理問題も入っている気がしますね。答えがないので難しいです…

418 contribution

ご丁寧な回答ありがとうございます。

前半の ECMAScript5 の件については、わたし自身コメントを修正(してしまってごめんなさい。あまりに酷いコメントで自分でいたたまれなくなり…)してますように、わたしが記事の意図を読めておりませんでした。

ご対応いただいた冒頭の注意書きでIE8に対する記事全体のスタンスがわかりやすくなり、とてもGoodだと思います。ありがとうございました。

後半のprototype汚染の件についてもご回答頂きました通りで、伝えたかったことは「for in が必ずしも悪くないんじゃない?」ではなく「for in と prototype 汚染、両方注意しておくのが自分にとって無難だという情報を、本文に追加する必要はないと思うけどコメントで補足的に共有してみますね」でした(我ながら超わかりにくいですね…)

次回からもう少し記事の文意を読み取り、また自分の文意がちゃんと伝わるようなコメントをつけるように気をつけます :bow:

「無理やりscript要素を生成しない、というか生成してはならない」も入れてほしいな。

5249 contribution

コメントありがとうございます。

無理やりscript要素を生成しない、というか生成してはならない

意図するところがあまり把握できていないので、頓珍漢な回答になるかもしれません。間違っていたら突っ込んでください。

無理矢理scriptタグを生成しない、というのはいわゆる

スクリプトタグの動的生成
// (以下document.readyStateがinteractive以降のステート状態とする)
var script = document.createElement("script");
script.textContent = "(" + callback.toString() + ")();";
document.head.appendChild(script);

function callback(){
    console.log("関数を呼び出したよ!");
}

っていう風に、関数を文字列化してtextContentとして<script>タグに埋め込むパターンのことでしょうか?

これに関しては私は許容してもいいかと考えています。

と言いますのも、UserScriptやChromeExtentionのようなプラグインの類では、サンドボックスが徹底されている関係、動的にサイト側に埋め込んでしまったほうが実装が楽な場面が存在するというのがあります。というか一部に関してはこれでないと実装できないものもあります。(もちろんやりたい内容にもよるんですが…。)

それともう一点理由があります。それは非同期ローディングによる高速化の話です。
一般的にこれはscriptタグに「async」属性を付与することで解決をします。

async属性による非同期ローディングスクリプトの例
<head>
    <!-- 省略 -->
    <script src="hoge.js" async></script>
    <script src="fuga.js" async></script>
    <script src="piyo.js" async></script>
    <!-- 省略 -->
</head>

しかし、問題はasync属性の実装状況で、今回対象の一つに加えているIE9で動作しないという問題があります。

http://caniuse.com/#feat=script-async

そこで、XMLHttpRequestを利用して非同期的に通信を行い動的にscriptタグを生成することで、これを解決するというパターンが実際に存在します。

XMLHttpRequestを利用した非同期並列通信による動的scriptタグ生成
// 読み込むjsファイルを配列に格納する
var urlList = ["hoge.js","fuga.js","piyo.js"];
// それらのファイルを並列に読み込む
loadMultiText(urlList,addScript);

// 与えられたテキストをスクリプトタグとして埋め込む
function addScript(text){
    var script = document.createElement("script");
    script.textContent = text;
    document.head.appendChild(script);
}

// 複数のURLを並列に読み込む
function loadMultiText(arr,callback){
    for(var i=0;i<arr.length;++i){
        loadText(arr[i],callback);
    }
}

// 単一のURLを読み込む
function loadText(src,callback){
    var xhr = new XMLHttpRequest();
    xhr.open("GET",src);
    xhr.onreadystatechange = function(){
        if(xhr.readyState !== 4) return;
        if(xhr.status === 0) throw new Error("通信失敗");
        if(xhr.status !== 200) throw new Error("リクエスト失敗(" + xhr.status + ")");
        // (今回はXHRが本質ではないので200以外は失敗とさせてください)

        // 通信が終わったら文字列をコールバック関数に渡す
        callback(xhr.responseText);
    };
    xhr.send();
}

(本当はcreateDocumentFragmentでまとめてappendChildするなど、まだまだチューニングすべき的点は有りますがこれも本質じゃないので省略とします。)

defer属性に関しては最悪の場合「bodyの末尾に埋め込む」っていう苦しい解決方法が存在しますが、async属性の場合にはこれ以外の解決方法を私は知りません。
知っていたら教えて頂けると幸いです。

以上2点により、無理矢理scriptを生成する場面というのは確実に存在すると私は考えています。なので生成してはならないとは思っておりません。

うーん…返信してもらっておいて申し訳ないですが、そもそも私はJavaScriptはほとんど詳しくないので、話が複雑だと話が入ってこないですね。
NoScript
https://noscript.net/
のJavaScriptを見てもらうと、というかアドオンを使ってもらうと話が早いのですが。

5249 contribution

返信ありがとうございます。そういうことでしたら申し訳ないのですが回答致しかねます。

まず、一連のコメントに問題点や課題が見当たらないので、nandaka_furariさんが何故

「無理やりscript要素を生成しない、というか生成してはならない」も入れてほしいな。

という主張に至ったのか、その経緯が不明です。

例えば、

「○○を使っていたら××ということが問題になったので、上記の主張に至った。」

のように、どういう状況で問題になったか、どういう課題が生まれたかなどを記載していただけると、こちらとしても回答できるようになります。

また、NoScriptについてこちらの方で調査しましたところ、Firefox向けのプラグインであることを把握しております。

私の方で、不用意にブラウザプラグインを導入して確かめる、という作業を行うのは流石に無理な相談なので、実際に使うことは出来ません。

そこで第二の提案として、NoScriptのソースコードも拝見しました。
しかし、NoScript内部には7000行近いコードがつらつらと書いてあったので、どこが該当の箇所か見当が付きませんでした。

これに加えて、先述の「問題点が不明」ということもあって、NoScriptのコード全体から、問題となる該当コードの特定には至りませんでした。

そしてなにより、この記事は(明確な定義での線引こそしてないものの)おおよそ現代的なブラウザ全般に対して、JSという言語の文法という視点から記事を書いております。

もし仮に、問題となる原因がNoScriptというプラグイン側に起因する問題であれば回答できません。

なぜなら、「NoScriptを使っていると不都合が生じるからやめて欲しい」という非常に限定された条件に対しての文言を、現代的な主要ブラウザ全般を対象としている本記事に記載することは出来ないからです。

以上、(NoScriptに起因しない)広く一般的に問題と言える内容の問題点がもしあれば、再度返信いただければと思います。その際には積極的に回答したいと考えております。

0 contribution

極めて善意かつ、面白い話題に切り替える方向で解釈するならば、
「ページ内のスクリプトは誰のものか」という問題に基づく発言なのかもしれないですね。

Webアプリ開発者からみると、既存の規格をベースにする以上、必要になる動的なscript要素追加も、
「おれはxx.jsを止めたい」というユーザから見ると、ただ面倒で汚い操作に見える。。ということなのではないでしょうか。

Webアプリ開発者にとって便利で、自分で機能を調整したいエンドユーザにも優しい、モジュールの仕組みが整えば良いのでしょうが、そこまではまだ全然到達していないのかなー。などと思っています。

(幸せは進化した先にしかない、というのが自分の考えですが。)

This comment has been deleted for violation of our Terms of Service.
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.