LoginSignup
0

More than 5 years have passed since last update.

ES6「for-of」構文が動かない問題

Last updated at Posted at 2018-02-25

現象

ios11だと動いていたJSが、ios10だと動かない。

原因

デバッグしたところ、そもそもJSファイルを読み込み時にエラーが出た。
ios10, ios9でfor-of構文が動かない。
※IEでも動かないみたい。

以下、実際のコード。

ios10,ios9で動かないコード
const items = document.getElementsByClassName('js_item');
for (const item of items) {
    item.addEventListener('click', e => {
        ...
    });
};

対応手順

以下、一つずつ解説しながら対応していく。

対応方法1

1. for-ofが動かないので、forEachに書き換える

修正前
const items = document.getElementsByClassName('js_item');
for (const item of items) { // for-of構文
    item.addEventListener('click', e => {
        ...
    });
};

修正後
const items = document.getElementsByClassName('js_item');
items.forEach(function(item) { // forEach構文
    item.addEventListener('click', e => {
        ...
    });
});

for-ofとは

オブジェクトのプロパティの値についての繰り返しを行うためのメソッド。
ES6(ES2015)から導入された構文。なので、ブラウザのバージョンが古い場合に動かない可能性がある。
もし動かない場合は、後述のArray.forEachで配列に対する繰り返しを行うことができる。

forEachとは

配列の各要素について繰り返し処理を行うためのメソッド。できることは前述のfor-ofと同じで、配列のみに適用可能。
Array.prototype.forEach() - JavaScript - MDN - Mozilla

2.「配列のようなオブジェクト」だとforEachが動かないので、配列に変換する

修正前
const items = document.getElementsByClassName('js_item'); //配列のようなオブジェクト
items.forEach(function(item) {
    item.addEventListener('click', e => {
        ...
    });
});

「配列のようなオブジェクト」であるitemsはArrayのメソッドを使えない。なので、itemsを配列に変換する。
callメソッドを使って、Array.prototype.sliceメソッドを実行し、新しい配列に変換する。

修正後
const items = Array.prototype.slice.call(document.getElementsByClassName('js_item'),0); //配列に変換
items.forEach(function(item) {
    item.addEventListener('click', e => {
        ...
    });
});

「配列のようなオブジェクト」とは

Array(配列)ぽいのにArrayのメソッドを持っていない「配列のようなオブジェクト」。
Arrayオブジェクトには多くのメソッドがあるが、「配列のようなオブジェクト」ではそのArray(配列)のメソッドが使えない。
なので、Array.prototype.forEach()が使えなかった。

「配列のようなオブジェクト」といえば、HTMLCollectionやNodeList、argumentsなどがある。

Array.prototype.sliceとは

配列の一部を取り出して新しい配列を返す。Arrayのメソッドなので、前述の「配列のようなオブジェクト」では使えない。

Array.prototype.slice() - JavaScript - MDN - Mozilla

callメソッドとは

本来使えないはずのメソッドを使うことができる。

 全ての関数が共通して持っているメソッド。全ての関数はFunctionクラスのオブジェクトで、callはFunction.prototypeの中にある。なので、関数宣言、関数式、new Functionのいずれの方法で関数を生成しても、callメソッドが継承される。
callメソッドの第一引数には、あるメソッドを実行させたいオブジェクトを指定する。

applyとcallの使い方を丁寧に説明してみる - あと味

例えば、以下。

callメソッドとは
class Hoge {
  constructor() {
    this.name = "hoge";
  };
  echo() {
    console.log(this.name);
  };
};
class Fuga {
  constructor() {
    this.name = "fuga";
  };
};

const h = new Hoge();
const f = new Fuga();

h.echo();       //「hoge」と表示される
f.echo();       //これはFugaに定義されていないので、使うことができない。
h.echo.call(f); //「fuga」と表示される

Fugaを「主体」として、Hogeが保有しているecho「メソッド」を実行することができる。

つまり、「Aが持っていないメソッド」.call(A);で、「Aが持っていないメソッド」を「A」で実行することができる。

Array.prototype.slice.call();

Array(配列)ぽいのにArrayのメソッドを持っていない「配列のようなオブジェクト」でも、Arrayのメソッドであるsliceメソッドを使うことができる。
なので、「配列のようなオブジェクト」を配列に変換することができる。

今回の場合、配列ではないdocument.getElementsByClassName('js_item')をcallメソッドを使って、Arrayのメソッドであるsliceメソッドを実行し、配列に変換している。

「配列のようなオブジェクト」を配列に変換する
const items = Array.prototype.slice.call(document.getElementsByClassName('js_item'),0); //配列に変換
items.forEach(function(item) {
    item.addEventListener('click', e => {
        ...
    });
});

以下、修正したコード全文

修正前
const items = document.getElementsByClassName('js_item');
for (const item of items) {
    item.addEventListener('click', e => {
        ...
    });
};
修正後
const items = Array.prototype.slice.call(document.getElementsByClassName('js_item'),0);
items.forEach(function(item) {
    item.addEventListener('click', e => {
        ...
    });
});

対応方法2

こちらの方が記述量が少なく済む。
もしかしたら、前述の対応方法1を読んでいた方の中には、こちらの方法でもいいのでは?と思った方もいるでしょう。

「配列のようなオブジェクト」だとforEachが動かないので、callメソッドを使ってforEachを実行する

修正前
const items = document.getElementsByClassName('js_item');
for (const item of items) {
    item.addEventListener('click', e => {
        ...
    });
};
修正後
const items = document.getElementsByClassName('js_item');
Array.prototype.forEach.call(items, function(item) { // callメソッドを使ってforEachを実行
    item.addEventListener('click', e => {
        ...
    });
});

そもそも「配列のようなオブジェクト」でもforEachを動かしたいのだから、Array.sliceメソッドを使わずにcallメソッドでforEachを実行すればいいのでは。

以下、修正したコード全文

修正前
const items = document.getElementsByClassName('js_item');
for (const item of items) {
    item.addEventListener('click', e => {
        ...
    });
};
修正後(対応方法1の場合)
const items = Array.prototype.slice.call(document.getElementsByClassName('js_item'),0);
items.forEach(function(item) {
    item.addEventListener('click', e => {
        ...
    });
});
修正後(対応方法2の場合)
const items = document.getElementsByClassName('js_item');
Array.prototype.forEach.call(items, function(item) {
    item.addEventListener('click', e => {
        ...
    });
});

修正してみて

なんでもES6など新しいのを使えばいいというわけではなかった。
対応ブラウザや仕組みを理解することが必要だと痛感した。
今回をきっかけに、Javascriptの配列についてもっと調べようと思った。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0