#現象
ios11だと動いていたJSが、ios10だと動かない。
#原因
デバッグしたところ、そもそもJSファイルを読み込み時にエラーが出た。
ios10, ios9でfor-of
構文が動かない。
※IEでも動かないみたい。
以下、実際のコード。
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の使い方を丁寧に説明してみる - あと味
例えば、以下。
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 => {
...
});
};
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');
Array.prototype.forEach.call(items, function(item) {
item.addEventListener('click', e => {
...
});
});
#修正してみて
なんでもES6など新しいのを使えばいいというわけではなかった。
対応ブラウザや仕組みを理解することが必要だと痛感した。
今回をきっかけに、Javascriptの配列についてもっと調べようと思った。